summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bindings/CMakeLists.txt12
-rw-r--r--src/bindings/swig/java/CMakeLists.txt31
-rw-r--r--src/bindings/swig/java/uscxml.i40
-rw-r--r--src/uscxml.h6
-rw-r--r--src/uscxml/Factory.cpp56
-rw-r--r--src/uscxml/Factory.h73
-rw-r--r--src/uscxml/Interpreter.cpp1712
-rw-r--r--src/uscxml/Interpreter.h314
-rw-r--r--src/uscxml/Message.cpp168
-rw-r--r--src/uscxml/Message.h122
-rw-r--r--src/uscxml/concurrency/BlockingQueue.h41
-rw-r--r--src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.cpp57
-rw-r--r--src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.h45
-rw-r--r--src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.cpp87
-rw-r--r--src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h52
-rw-r--r--src/uscxml/concurrency/tinythread.cpp303
-rw-r--r--src/uscxml/concurrency/tinythread.h714
-rw-r--r--src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp289
-rw-r--r--src/uscxml/datamodel/ecmascript/v8/V8DataModel.h73
-rw-r--r--src/uscxml/ioprocessor/basichttp/README.md2
-rw-r--r--src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp361
-rw-r--r--src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h96
-rw-r--r--src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.cpp3
-rw-r--r--src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.h15
-rw-r--r--src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.cpp74
-rw-r--r--src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h47
26 files changed, 4793 insertions, 0 deletions
diff --git a/src/bindings/CMakeLists.txt b/src/bindings/CMakeLists.txt
new file mode 100644
index 0000000..deac396
--- /dev/null
+++ b/src/bindings/CMakeLists.txt
@@ -0,0 +1,12 @@
+find_package(SWIG)
+if (SWIG_FOUND)
+ if(SWIG_VERSION VERSION_GREATER 2.0.4)
+ MARK_AS_ADVANCED(SWIG_DIR SWIG_EXECUTABLE SWIG_VERSION)
+ INCLUDE(${SWIG_USE_FILE})
+ add_subdirectory(swig/java)
+ else()
+ message("SWIG version 2.0.5 is required, found ${SWIG_VERSION} - skipping java wrapper generation")
+ endif()
+else()
+ message("SWIG not found - skipping wrapper generation")
+endif()
diff --git a/src/bindings/swig/java/CMakeLists.txt b/src/bindings/swig/java/CMakeLists.txt
new file mode 100644
index 0000000..a729b8e
--- /dev/null
+++ b/src/bindings/swig/java/CMakeLists.txt
@@ -0,0 +1,31 @@
+# generate JNI library and create a jar
+# Make from within Eclipse fails miserably with the whole thing
+
+find_package(JNI)
+if(JNI_FOUND)
+ include_directories(${JNI_INCLUDE_DIRS})
+else()
+ message(STATUS "No JNI libraries found - not building Java wrappers")
+ return()
+endif()
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+
+SET(CMAKE_SWIG_FLAGS "")
+SET(SCXMLPL_JAVA_PACKAGE "org.uscxml")
+SET(SCXMLPL_JAVA_DIR "org/uscxml")
+
+# we need ; to produce a space with the package .. weird
+SET_SOURCE_FILES_PROPERTIES(uscxml.i PROPERTIES SWIG_FLAGS "-package;${SCXMLPL_JAVA_PACKAGE}")
+SET_SOURCE_FILES_PROPERTIES(uscxml.i PROPERTIES CPLUSPLUS ON)
+SET(CMAKE_SWIG_OUTDIR "${CMAKE_CURRENT_BINARY_DIR}/${SCXMLPL_JAVA_DIR}")
+
+SWIG_ADD_MODULE(uscxmlNativeJava java uscxml.i)
+foreach(JNI_LIBRARY ${JNI_LIBRARIES})
+ if (NOT ${JNI_LIBRARY} MATCHES ".*jawt.*")
+ SWIG_LINK_LIBRARIES(uscxmlNativeJava ${JNI_LIBRARY})
+ endif()
+endforeach()
+set_target_properties(uscxmlNativeJava PROPERTIES FOLDER "Bindings")
+
+swig_link_libraries(uscxmlNativeJava uscxml)
diff --git a/src/bindings/swig/java/uscxml.i b/src/bindings/swig/java/uscxml.i
new file mode 100644
index 0000000..ccd6fe0
--- /dev/null
+++ b/src/bindings/swig/java/uscxml.i
@@ -0,0 +1,40 @@
+%module(directors="1", allprotected="1") uscxmlNativeJava
+
+// import swig typemaps
+//%include <arrays_java.i>
+//%include <inttypes.i>
+%include <boost_shared_ptr.i>
+
+// disable warning related to unknown base class
+#pragma SWIG nowarn=401
+//%ignore boost::enable_shared_from_this;
+
+%javaconst(1);
+
+# %shared_ptr(uscxml::dom::Element);
+# %shared_ptr(uscxml::dom::Executable);
+
+
+//**************************************************
+// This ends up in the generated wrapper code
+//**************************************************
+
+%{
+
+#include "../../../uscxml/Message.h"
+#include "../../../uscxml/Interpreter.h"
+
+using namespace uscxml;
+
+%}
+
+%rename(toString) operator<<;
+
+
+//***********************************************
+// Parse the header file to generate wrappers
+//***********************************************
+
+%include "../../../uscxml/Message.h"
+%include "../../../uscxml/Interpreter.h"
+
diff --git a/src/uscxml.h b/src/uscxml.h
new file mode 100644
index 0000000..792ba43
--- /dev/null
+++ b/src/uscxml.h
@@ -0,0 +1,6 @@
+#ifndef USCXML_H_2WZ0PBQH
+#define USCXML_H_2WZ0PBQH
+
+#include "uscxml/Interpreter.h"
+
+#endif /* end of include guard: USCXML_H_2WZ0PBQH */
diff --git a/src/uscxml/Factory.cpp b/src/uscxml/Factory.cpp
new file mode 100644
index 0000000..91da562
--- /dev/null
+++ b/src/uscxml/Factory.cpp
@@ -0,0 +1,56 @@
+#include "uscxml/Factory.h"
+#include "uscxml/datamodel/ecmascript/v8/V8DataModel.h"
+//#include "uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h"
+#include "uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h"
+
+namespace uscxml {
+
+ Factory::Factory() {
+ _dataModels["ecmascript"] = new V8DataModel();
+// _ioProcessors["basichttp"] = new PionIOProcessor();
+ _ioProcessors["basichttp"] = new io::libevent::EventIOProcessor();
+ }
+
+ void Factory::registerIOProcessor(const std::string type, IOProcessor* ioProcessor) {
+ getInstance()->_ioProcessors[type] = ioProcessor;
+ }
+
+ void Factory::registerDataModel(const std::string type, DataModel* dataModel) {
+ getInstance()->_dataModels[type] = dataModel;
+ }
+
+ void Factory::registerExecutableContent(const std::string tag, ExecutableContent* executableContent) {
+ getInstance()->_executableContent[tag] = executableContent;
+ }
+
+ DataModel* Factory::getDataModel(const std::string type, Interpreter* interpreter) {
+ if (Factory::getInstance()->_dataModels.find(type) != getInstance()->_dataModels.end()) {
+ return getInstance()->_dataModels[type]->create(interpreter);
+ }
+ return NULL;
+ }
+
+ IOProcessor* Factory::getIOProcessor(const std::string type, Interpreter* interpreter) {
+ if (getInstance()->_ioProcessors.find(type) != getInstance()->_ioProcessors.end()) {
+ return getInstance()->_ioProcessors[type]->create(interpreter);
+ }
+ return NULL;
+ }
+
+ ExecutableContent* Factory::getExecutableContent(const std::string tag, Interpreter* interpreter) {
+ if (getInstance()->_executableContent.find(tag) != getInstance()->_executableContent.end()) {
+ return getInstance()->_executableContent[tag]->create(interpreter);
+ }
+ return NULL;
+ }
+
+ Factory* Factory::getInstance() {
+ if (_instance == NULL) {
+ _instance = new Factory();
+ }
+ return _instance;
+ }
+
+ Factory* Factory::_instance = NULL;
+
+} \ No newline at end of file
diff --git a/src/uscxml/Factory.h b/src/uscxml/Factory.h
new file mode 100644
index 0000000..f1840c6
--- /dev/null
+++ b/src/uscxml/Factory.h
@@ -0,0 +1,73 @@
+#ifndef FACTORY_H_5WKLGPRB
+#define FACTORY_H_5WKLGPRB
+
+#include "uscxml/Message.h"
+
+#include <string>
+
+namespace uscxml {
+
+ class Interpreter;
+
+ class ExecutableContent {
+ public:
+ ExecutableContent() {};
+ virtual ExecutableContent* create(Interpreter* interpreter) = 0;
+ };
+
+ class IOProcessor {
+ public:
+ IOProcessor() {};
+ virtual ~IOProcessor() {};
+ virtual IOProcessor* create(Interpreter* interpreter) = 0;
+
+ virtual std::string getURL() = 0;
+ virtual void send(SendRequest& req) = 0;
+ virtual void invoke(InvokeRequest& req) = 0;
+ virtual void cancel(const std::string sendId) = 0;
+ };
+
+ class DataModel {
+ public:
+ virtual DataModel* create(Interpreter* interpreter) = 0;
+
+ virtual bool validate(const std::string& location, const std::string& schema) = 0;
+ virtual void setEvent(Event& event) = 0;
+ virtual void setData(const std::string& key, Data& event) = 0;
+
+ // foreach
+ virtual uint32_t getLength(const std::string& expr) = 0;
+ virtual void pushContext() = 0;
+ virtual void popContext() = 0;
+
+ virtual void eval(const std::string& expr) = 0;
+ virtual std::string evalAsString(const std::string& expr) = 0;
+ virtual bool evalAsBool(const std::string& expr) = 0;
+ virtual void assign(const std::string& location, const std::string& expr) = 0;
+ };
+
+ class Factory {
+ public:
+ static void registerIOProcessor(const std::string type, IOProcessor* ioProcessor);
+ static void registerDataModel(const std::string type, DataModel* dataModel);
+ static void registerExecutableContent(const std::string tag, ExecutableContent* executableContent);
+
+ static DataModel* getDataModel(const std::string type, Interpreter* interpreter);
+ static IOProcessor* getIOProcessor(const std::string type, Interpreter* interpreter);
+ static ExecutableContent* getExecutableContent(const std::string tag, Interpreter* interpreter);
+ static Factory* getInstance();
+
+ std::map<std::string, DataModel*> _dataModels;
+ std::map<std::string, IOProcessor*> _ioProcessors;
+ std::map<std::string, ExecutableContent*> _executableContent;
+
+ protected:
+ Factory();
+ static Factory* _instance;
+
+ };
+
+
+}
+
+#endif /* end of include guard: FACTORY_H_5WKLGPRB */
diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp
new file mode 100644
index 0000000..7189f12
--- /dev/null
+++ b/src/uscxml/Interpreter.cpp
@@ -0,0 +1,1712 @@
+#include "uscxml/Interpreter.h"
+
+#include <DOM/Simple/DOMImplementation.hpp>
+
+#include <boost/uuid/uuid.hpp>
+#include <boost/uuid/uuid_generators.hpp>
+#include <boost/uuid/uuid_io.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/tokenizer.hpp>
+
+#include <glog/logging.h>
+
+#include <assert.h>
+#include <algorithm>
+
+namespace uscxml {
+
+using namespace Arabica::XPath;
+using namespace Arabica::DOM;
+
+boost::uuids::random_generator Interpreter::uuidGen;
+const std::string Interpreter::getUUID() {
+ return boost::lexical_cast<std::string>(uuidGen());
+}
+
+Interpreter::Interpreter(const std::string& url) {
+ _thread = NULL;
+ _url = url;
+
+ LOG(INFO) << "runtime started with " << _url;
+ Arabica::SAX::InputSource<std::string> inputSource(_url);
+
+ Arabica::SAX2DOM::Parser<std::string> domParser;
+ Arabica::SAX::CatchErrorHandler<std::string> errorHandler;
+ domParser.setErrorHandler(errorHandler);
+ if(!domParser.parse(inputSource) || !domParser.getDocument().hasChildNodes()) {
+ LOG(INFO) << "could not parse " << _url << ":";
+ if(errorHandler.errorsReported()) {
+ LOG(ERROR) << errorHandler.errors() << std::endl;
+ } else {
+ Arabica::SAX::InputSourceResolver resolver(inputSource, Arabica::default_string_adaptor<std::string>());
+ if (!resolver.resolve()) {
+ LOG(ERROR) << "no such file";
+ }
+ }
+ } else {
+ _doc = domParser.getDocument();
+ }
+
+ if (_doc) {
+ // do we have a xmlns attribute?
+ std::string ns = _doc.getDocumentElement().getNamespaceURI();
+ if(ns.size() > 0) {
+ _nsContext.addNamespaceDeclaration(ns, "sc");
+ _xpath.setNamespaceContext(_nsContext);
+ _nsPrefix = "sc:";
+ }
+ NodeList<std::string> scxmls = _doc.getElementsByTagName("scxml");
+ if (scxmls.getLength() > 0) {
+ _scxml = (Arabica::DOM::Element<std::string>)scxmls.item(0);
+ } else {
+ LOG(ERROR) << "Cannot find SCXML element" << std::endl;
+ }
+ _name = (HAS_ATTR(_scxml, "name") ? ATTR(_scxml, "name") : getUUID());
+ }
+}
+
+Interpreter::~Interpreter() {
+ std::map<std::string, IOProcessor*>::iterator ioProcessorIter = _ioProcessors.begin();
+ while(ioProcessorIter != _ioProcessors.end()) {
+ delete ioProcessorIter->second;
+ ioProcessorIter++;
+ }
+ if (_thread) {
+ _thread->join();
+ delete(_thread);
+ }
+}
+
+void Interpreter::start() {
+ _thread = new tthread::thread(Interpreter::run, this);
+}
+
+void Interpreter::stop() {
+}
+
+void Interpreter::run(void* instance) {
+ ((Interpreter*)instance)->interpret();
+}
+
+void Interpreter::waitForStabilization() {
+ tthread::lock_guard<tthread::mutex> lock(_mutex);
+ _stabilized.wait(_mutex);
+}
+
+// see: http://www.w3.org/TR/scxml/#AlgorithmforSCXMLInterpretation
+void Interpreter::interpret() {
+ if (!_scxml)
+ return;
+// dump();
+
+ _sessionId = getUUID();
+ normalize(_doc);
+
+ if(HAS_ATTR(_scxml, "datamodel")) {
+ _dataModel = Factory::getDataModel(ATTR(_scxml, "datamodel"), this);
+ if(_dataModel == NULL) {
+ LOG(ERROR) << "No datamodel for " << ATTR(_scxml, "datamodel") << " registered";
+ return;
+ }
+ }
+
+ setupIOProcessors();
+
+ // executeGlobalScriptElements
+ NodeSet<std::string> globalScriptElems = _xpath.evaluate("/" + _nsPrefix + "scxml/" + _nsPrefix + "script", _doc).asNodeSet();
+ for (unsigned int i = 0; i < globalScriptElems.size(); i++) {
+// std::cout << globalScriptElems[i].getFirstChild().getNodeValue() << std::endl;
+ if (_dataModel)
+ executeContent(globalScriptElems[i]);
+ }
+
+ _running = true;
+
+ std::string binding = _xpath.evaluate("/" + _nsPrefix + "scxml/@binding", _doc).asString();
+ _binding = (boost::iequals(binding, "late") ? LATE : EARLY);
+
+ // initialize all data elements
+ if (_dataModel && _binding == EARLY) {
+ NodeSet<std::string> dataElems = _xpath.evaluate("//" + _nsPrefix + "data", _doc).asNodeSet();
+ for (unsigned int i = 0; i < dataElems.size(); i++) {
+ initializeData(dataElems[i]);
+ }
+ } else if(_dataModel) {
+ NodeSet<std::string> topDataElems = _xpath.evaluate("/" + _nsPrefix + "scxml/" + _nsPrefix + "datamodel/" + _nsPrefix + "data", _doc).asNodeSet();
+ for (unsigned int i = 0; i < topDataElems.size(); i++) {
+ initializeData(topDataElems[i]);
+ }
+ }
+
+ // executeTransitionContent
+ Arabica::DOM::Element<std::string> initialState = (Arabica::DOM::Element<std::string>)getInitialState();
+
+ // create a pseudo initial and transition element
+ NodeSet<std::string> initialTransitions;
+ Arabica::DOM::Element<std::string> initialElem = _doc.createElement("initial");
+ Arabica::DOM::Element<std::string> transitionElem = _doc.createElement("transition");
+ transitionElem.setAttribute("target", initialState.getAttribute("id"));
+
+ initialElem.appendChild(transitionElem);
+ _scxml.appendChild(initialElem);
+
+ assert(boost::iequals(TAGNAME(initialElem), "initial"));
+
+ initialTransitions.push_back(transitionElem);
+ enterStates(initialTransitions);
+
+ mainEventLoop();
+}
+
+void Interpreter::initializeData(const Arabica::DOM::Node<std::string>& data) {
+ if (!_dataModel) {
+ LOG(ERROR) << "Cannot initialize data when no datamodel is given!";
+ return;
+ }
+ try {
+ if (!HAS_ATTR(data, "id"))
+ return;
+ std::string value = "undefined";
+
+ if (HAS_ATTR(data, "expr")) {
+ value = ATTR(data, "expr");
+ goto VALUE_INITIALIZED;
+ }
+ if (HAS_ATTR(data, "src")) {
+ Arabica::SAX::InputSourceResolver resolver(Arabica::SAX::InputSource<std::string>(ATTR(data, "src")),
+ Arabica::default_string_adaptor<std::string>());
+ value = std::string(std::istreambuf_iterator<char>(*resolver.resolve()), std::istreambuf_iterator<char>());
+ goto VALUE_INITIALIZED;
+ }
+ if (data.hasChildNodes()) {
+ // search for the text node with the actual script
+ if (data.getFirstChild().getNodeType() == Node_base::TEXT_NODE) {
+ value = data.getFirstChild().getNodeValue();
+ }
+ }
+
+VALUE_INITIALIZED:
+ _dataModel->assign(ATTR(data, "id"), value);
+
+ } catch (Event e) {
+ LOG(ERROR) << "Syntax error in send element:" << std::endl << e << std::endl;
+ }
+}
+
+void Interpreter::normalize(const Arabica::DOM::Node<std::string>& node) {
+ // make sure every state has an id and set isFirstEntry to true
+ Arabica::XPath::NodeSet<std::string> states = _xpath.evaluate("//" + _nsPrefix + "state", _doc).asNodeSet();
+ for (int i = 0; i < states.size(); i++) {
+ Arabica::DOM::Element<std::string> stateElem = Arabica::DOM::Element<std::string>(states[i]);
+ stateElem.setAttribute("isFirstEntry", "true");
+ if (!stateElem.hasAttribute("id")) {
+ stateElem.setAttribute("id", getUUID());
+ }
+ }
+
+ Arabica::XPath::NodeSet<std::string> finals = _xpath.evaluate("//" + _nsPrefix + "final", _doc).asNodeSet();
+ for (int i = 0; i < finals.size(); i++) {
+ Arabica::DOM::Element<std::string> finalElem = Arabica::DOM::Element<std::string>(finals[i]);
+ finalElem.setAttribute("isFirstEntry", "true");
+ if (!finalElem.hasAttribute("id")) {
+ finalElem.setAttribute("id", getUUID());
+ }
+ }
+
+ Arabica::XPath::NodeSet<std::string> histories = _xpath.evaluate("//" + _nsPrefix + "history", _doc).asNodeSet();
+ for (int i = 0; i < histories.size(); i++) {
+ Arabica::DOM::Element<std::string> historyElem = Arabica::DOM::Element<std::string>(histories[i]);
+ if (!historyElem.hasAttribute("id")) {
+ historyElem.setAttribute("id", getUUID());
+ }
+ }
+
+ Arabica::XPath::NodeSet<std::string> scxml = _xpath.evaluate("/" + _nsPrefix + "scxml", _doc).asNodeSet();
+ if (!((Arabica::DOM::Element<std::string>)scxml[0]).hasAttribute("id")) {
+ ((Arabica::DOM::Element<std::string>)scxml[0]).setAttribute("id", getUUID());
+ }
+}
+
+void Interpreter::mainEventLoop() {
+ while(_running) {
+ NodeSet<std::string> enabledTransitions;
+ _stable = false;
+
+ // Here we handle eventless transitions and transitions
+ // triggered by internal events until machine is stable
+ while(_running && !_stable) {
+#if 0
+ std::cout << "Configuration: ";
+ for (int i = 0; i < _configuration.size(); i++) {
+ std::cout << ((Arabica::DOM::Element<std::string>)_configuration[i]).getAttribute("id") << ", ";
+ }
+ std::cout << std::endl;
+#endif
+ enabledTransitions = selectEventlessTransitions();
+ if (enabledTransitions.size() == 0) {
+ if (_internalQueue.size() == 0) {
+ _stable = true;
+ } else {
+ Event internalEvent = _internalQueue.front();
+ _internalQueue.pop_front();
+ _dataModel->setEvent(internalEvent);
+ enabledTransitions = selectTransitions(internalEvent.name);
+ }
+ }
+ if (!enabledTransitions.empty())
+ microstep(enabledTransitions);
+ }
+
+ for (unsigned int i = 0; i < _statesToInvoke.size(); i++) {
+ NodeSet<std::string> invokes = _xpath.evaluate("" + _nsPrefix + "invoke", _statesToInvoke[i]).asNodeSet();
+ for (unsigned int j = 0; j < invokes.size(); j++) {
+ // @TODO:
+ // boost::shared_ptr<Invoke> invoke = boost::static_pointer_cast<Invoke>(interpreter::Factory::create("invoke", invokes[j]));
+ // invoke->invoke(this);
+ }
+ }
+
+ _statesToInvoke = NodeSet<std::string>();
+ if (!_internalQueue.empty())
+ continue;
+
+ {
+ tthread::lock_guard<tthread::mutex> lock(_mutex);
+ _stabilized.notify_all();
+ }
+
+ Event externalEvent = _externalQueue.pop();
+ if (!_running)
+ exitInterpreter();
+
+ if (_dataModel && boost::iequals(externalEvent.name, "cancel.invoke." + _sessionId))
+ break;
+
+ if (_dataModel)
+ _dataModel->setEvent(externalEvent);
+ for (unsigned int i = 0; i < _configuration.size(); i++) {
+ NodeSet<std::string> invokes = _xpath.evaluate("" + _nsPrefix + "invoke", _configuration[i]).asNodeSet();
+ for (unsigned int j = 0; j < invokes.size(); j++) {
+ std::string invokeId = ((Arabica::DOM::Element<std::string>)invokes[i]).getAttribute("id");
+ std::string autoForward = ((Arabica::DOM::Element<std::string>)invokes[i]).getAttribute("autoforward");
+ if (boost::iequals(invokeId, externalEvent.invokeid)) {
+ // @TODO
+ // boost::shared_ptr<Invoke> invoke = boost::static_pointer_cast<Invoke>(interpreter::Factory::create("invoke", invokes[j]));
+ // invoke->applyFinalize(this);
+ }
+ if (autoForward.length() > 0) {
+ send(invokeId, externalEvent);
+ }
+ }
+ }
+ enabledTransitions = selectTransitions(externalEvent.name);
+ if (!enabledTransitions.empty())
+ microstep(enabledTransitions);
+ }
+ exitInterpreter();
+}
+
+void Interpreter::send(const std::string invokeId, Event& event) {
+}
+
+void Interpreter::send(const Arabica::DOM::Node<std::string>& element) {
+ SendRequest sendReq;
+ try {
+ // event
+ if (HAS_ATTR(element, "eventexpr") && _dataModel) {
+ sendReq.name = _dataModel->evalAsString(ATTR(element, "eventexpr"));
+ } else if (HAS_ATTR(element, "event")) {
+ sendReq.name = ATTR(element, "event");
+ } else {
+ LOG(ERROR) << "send element does not feature event, or eventexpr without datamodel given";
+ return;
+ }
+ // target
+ if (HAS_ATTR(element, "targetexpr") && _dataModel) {
+ sendReq.target = _dataModel->evalAsString(ATTR(element, "targetexpr"));
+ } else if (HAS_ATTR(element, "target")) {
+ sendReq.target = ATTR(element, "target");
+ }
+ // type
+ if (HAS_ATTR(element, "typeexpr") && _dataModel) {
+ sendReq.type = _dataModel->evalAsString(ATTR(element, "typeexpr"));
+ } else if (HAS_ATTR(element, "type")) {
+ sendReq.type = ATTR(element, "type");
+ } else {
+ sendReq.type = "http://www.w3.org/TR/scxml/#SCXMLEventProcessor";
+ }
+ // id
+ if (HAS_ATTR(element, "idlocation") && _dataModel) {
+ sendReq.sendid = _dataModel->evalAsString(ATTR(element, "idlocation"));
+ } else if (HAS_ATTR(element, "id")) {
+ sendReq.sendid = ATTR(element, "id");
+ } else {
+ /*
+ * The ids for <send> and <invoke> are subtly different. In a conformant
+ * SCXML document, they must be unique within the session, but in the case
+ * where the author does not provide them, the processor must generate a
+ * new unique ID not at load time but each time the element is executed.
+ * Furthermore the attribute 'idlocation' can be used to capture this
+ * automatically generated id. Finally note that the automatically generated
+ * id for <invoke> has a special format. See 6.4.1 Attribute Details for
+ * details. The SCXML processor may generate all other ids in any format,
+ * as long as they are unique.
+ */
+ sendReq.sendid = getUUID();
+ }
+ /** @TODO:
+ *
+ * If 'idlocation' is present, the SCXML Processor must generate an id when
+ * the parent <send> element is evaluated and store it in this location.
+ * See 3.14 IDs for details.
+ *
+ */
+
+ // delay
+ std::string delay;
+ sendReq.delayMs = 0;
+ if (HAS_ATTR(element, "delayexpr") && _dataModel) {
+ delay = _dataModel->evalAsString(ATTR(element, "delayexpr"));
+ } else if (HAS_ATTR(element, "delay")) {
+ delay = ATTR(element, "delay");
+ }
+ if (delay.size() > 0) {
+ boost::trim(delay);
+ std::stringstream delayTime;
+ if (delay.size() > 2 && boost::iequals("ms", delay.substr(delay.length() - 2, 2))) {
+ delayTime << delay.substr(0, delay.size() - 2);
+ delayTime >> sendReq.delayMs;
+ } else if (delay.size() > 1 && boost::iequals("s", delay.substr(delay.length() - 1, 1))) {
+ delayTime << delay.substr(0, delay.size() - 1);
+ delayTime >> sendReq.delayMs;
+ sendReq.delayMs *= 1000;
+ } else {
+ LOG(ERROR) << "Cannot make sense of delay value " << delay << ": does not end in 's' or 'ms'";
+ }
+ }
+ // namelist
+ if (HAS_ATTR(element, "namelist")) {
+ std::vector<std::string> names = tokenizeIdRefs(ATTR(element, "namelist"));
+ for (int i = 0; i < names.size(); i++) {
+ sendReq.namelist[names[i]] = _dataModel->evalAsString(names[i]);
+ }
+ }
+
+ // params
+ NodeSet<std::string> params = _xpath.evaluate("" + _nsPrefix + "param", element).asNodeSet();
+ for (int i = 0; i < params.size(); i++) {
+ if (!HAS_ATTR(params[i], "name")) {
+ LOG(ERROR) << "param element is missing name attribut";
+ continue;
+ }
+ std::string paramValue;
+ if (HAS_ATTR(params[i], "expr") && _dataModel) {
+ std::string location = _dataModel->evalAsString(ATTR(params[i], "expr"));
+ paramValue = _dataModel->evalAsString(location);
+ } else if(HAS_ATTR(params[i], "location") && _dataModel) {
+ paramValue = _dataModel->evalAsString(ATTR(params[i], "location"));
+ } else {
+ LOG(ERROR) << "param element is missing expr or location or no datamodel is specified";
+ continue;
+ }
+ sendReq.params[ATTR(params[i], "name")] = paramValue;
+ }
+
+ // content
+ NodeSet<std::string> contents = _xpath.evaluate("" + _nsPrefix + "content", element).asNodeSet();
+ if (contents.size() > 1)
+ LOG(ERROR) << "Only a single content element is allowed for send elements - using first one";
+ if (contents.size() > 0) {
+ if (HAS_ATTR(contents[0], "expr")) {
+ if (_dataModel) {
+ sendReq.content = _dataModel->evalAsString(ATTR(contents[0], "expr"));
+ } else {
+ LOG(ERROR) << "content element has expr attribute but no datamodel is specified.";
+ }
+ } else if (contents[0].hasChildNodes()) {
+// dump(contents[0].getFirstChild());
+ sendReq.content = contents[0].getFirstChild().getNodeValue();
+ } else {
+ LOG(ERROR) << "content element does not specify any content.";
+ }
+ }
+
+ IOProcessor* ioProc = getIOProcessor(sendReq.type);
+ if (ioProc != NULL) {
+ _ioProcessorsIds[sendReq.sendid] = ioProc;
+ ioProc->send(sendReq);
+ }
+
+ } catch (Event e) {
+ LOG(ERROR) << "Syntax error in send element:" << std::endl << e << std::endl;
+ }
+}
+
+void Interpreter::invoke(const Arabica::DOM::Node<std::string>& element) {
+ InvokeRequest invokeReq;
+ try {
+ // type
+ if (HAS_ATTR(element, "typeexpr") && _dataModel) {
+ invokeReq.type = _dataModel->evalAsString(ATTR(element, "typeexpr"));
+ } else if (HAS_ATTR(element, "type")) {
+ invokeReq.type = ATTR(element, "type");
+ } else {
+ LOG(ERROR) << "invoke element is missing expr or typeexpr or no datamodel is specified";
+ }
+
+ // src
+ if (HAS_ATTR(element, "srcexpr") && _dataModel) {
+ invokeReq.src = _dataModel->evalAsString(ATTR(element, "srcexpr"));
+ } else if (HAS_ATTR(element, "src")) {
+ invokeReq.src = ATTR(element, "src");
+ } else {
+ LOG(ERROR) << "invoke element is missing src or srcexpr or no datamodel is specified";
+ }
+
+ // id
+ if (HAS_ATTR(element, "idlocation") && _dataModel) {
+ invokeReq.invokeid = _dataModel->evalAsString(ATTR(element, "idlocation"));
+ } else if (HAS_ATTR(element, "id")) {
+ invokeReq.invokeid = ATTR(element, "id");
+ } else {
+ invokeReq.invokeid = getUUID();
+ }
+
+ // namelist
+ if (HAS_ATTR(element, "namelist")) {
+ invokeReq.namelist = ATTR(element, "namelist");
+ }
+
+ // autoforward
+ if (HAS_ATTR(element, "autoforward")) {
+ if (boost::iequals(ATTR(element, "autoforward"), "true")) {
+ invokeReq.autoForward = true;
+ }
+ } else {
+ invokeReq.autoForward = false;
+ }
+
+ // params
+ NodeSet<std::string> params = _xpath.evaluate("" + _nsPrefix + "param", element).asNodeSet();
+ for (int i = 0; i < params.size(); i++) {
+ if (HAS_ATTR(params[i], "name")) {
+ LOG(ERROR) << "param element is missing name attribut";
+ continue;
+ }
+ std::string paramValue;
+ if (HAS_ATTR(params[i], "expr") && _dataModel) {
+ paramValue = _dataModel->evalAsString(ATTR(params[i], "expr"));
+ } else if(HAS_ATTR(params[i], "location") && _dataModel) {
+ paramValue = _dataModel->evalAsString(ATTR(params[i], "location"));
+ } else {
+ LOG(ERROR) << "param element is missing expr or location or no datamodel is specified";
+ continue;
+ }
+ invokeReq.params[ATTR(params[i], "name")] = paramValue;
+ }
+
+ // content
+ NodeSet<std::string> contents = _xpath.evaluate("" + _nsPrefix + "content", element).asNodeSet();
+ if (contents.size() > 1)
+ LOG(ERROR) << "Only a single content element is allowed for send elements - using first one";
+ if (contents.size() > 0) {
+ invokeReq.content = contents[0].getNodeValue();
+ }
+
+ IOProcessor* ioProc = getIOProcessor(invokeReq.type);
+ if (ioProc != NULL) {
+ _ioProcessorsIds[invokeReq.invokeid] = ioProc;
+ ioProc->invoke(invokeReq);
+ }
+
+ } catch (Event e) {
+ LOG(ERROR) << "Syntax error in invoke element:" << std::endl << e << std::endl;
+ }
+
+}
+
+Arabica::XPath::NodeSet<std::string> Interpreter::selectTransitions(const std::string& event) {
+ Arabica::XPath::NodeSet<std::string> enabledTransitions;
+
+ NodeSet<std::string> atomicStates;
+ for (unsigned int i = 0; i < _configuration.size(); i++) {
+ if (isAtomic(_configuration[i]))
+ atomicStates.push_back(_configuration[i]);
+ }
+ atomicStates.to_document_order();
+
+ for (unsigned int i = 0; i < atomicStates.size(); i++) {
+ NodeSet<std::string> ancestors = getProperAncestors(atomicStates[i], Arabica::DOM::Node<std::string>());
+ ancestors.push_back(atomicStates[i]);
+ for (unsigned int j = 0; j < ancestors.size(); j++) {
+ NodeSet<std::string> transitions = _xpath.evaluate("" + _nsPrefix + "transition", ancestors[j]).asNodeSet();
+ for (unsigned int k = 0; k < transitions.size(); k++) {
+ if (((Arabica::DOM::Element<std::string>)transitions[k]).hasAttribute("event") &&
+ nameMatch(((Arabica::DOM::Element<std::string>)transitions[k]).getAttribute("event"), event) &&
+ hasConditionMatch(transitions[k])) {
+ enabledTransitions.push_back(transitions[k]);
+ goto LOOP;
+ }
+ }
+ }
+ LOOP:;
+ }
+ return enabledTransitions;
+}
+
+// see: http://www.w3.org/TR/scxml/#EventDescriptors
+bool Interpreter::nameMatch(const std::string& transitionEvent, const std::string& event) {
+ assert(transitionEvent.size() > 0);
+ assert(event.size() > 0);
+
+ // naive case of single descriptor and exact match
+ if (boost::equals(transitionEvent, event))
+ return true;
+
+ boost::char_separator<char> sep(" ");
+ boost::tokenizer<boost::char_separator<char> > tokens(transitionEvent, sep);
+ boost::tokenizer<boost::char_separator<char> >::iterator tokenIter = tokens.begin();
+
+ while(tokenIter != tokens.end()) {
+ std::string eventDesc(*tokenIter++);
+
+ // remove optional trailing .* for CCXML compatibility
+ if (eventDesc.find("*", eventDesc.size() - 1) != std::string::npos)
+ eventDesc = eventDesc.substr(0, eventDesc.size() - 1);
+ if (eventDesc.find(".", eventDesc.size() - 1) != std::string::npos)
+ eventDesc = eventDesc.substr(0, eventDesc.size() - 1);
+
+ // are they already equal?
+ if (boost::equals(eventDesc, event))
+ return true;
+
+ // eventDesc has to be a real prefix of event now and therefore shorter
+ if (eventDesc.size() >= event.size())
+ continue;
+
+ // it is a prefix of the event name and event continues with .something
+ if (eventDesc.compare(event.substr(0, eventDesc.size())) == 0)
+ if (event.find(".", eventDesc.size()) == eventDesc.size())
+ return true;
+ }
+ return false;
+}
+
+Arabica::XPath::NodeSet<std::string> Interpreter::selectEventlessTransitions() {
+ Arabica::XPath::NodeSet<std::string> enabledTransitions;
+
+ NodeSet<std::string> atomicStates;
+ for (unsigned int i = 0; i < _configuration.size(); i++) {
+ if (isAtomic(_configuration[i]))
+ atomicStates.push_back(_configuration[i]);
+ }
+ atomicStates.to_document_order();
+
+ for (unsigned int i = 0; i < atomicStates.size(); i++) {
+ NodeSet<std::string> ancestors = getProperAncestors(atomicStates[i], Arabica::DOM::Node<std::string>());
+ ancestors.push_back(atomicStates[i]);
+ for (unsigned int j = 0; j < ancestors.size(); j++) {
+ NodeSet<std::string> transitions = _xpath.evaluate("" + _nsPrefix + "transition", ancestors[j]).asNodeSet();
+ for (unsigned int k = 0; k < transitions.size(); k++) {
+ if (!((Arabica::DOM::Element<std::string>)transitions[k]).hasAttribute("event") && hasConditionMatch(transitions[k])) {
+ enabledTransitions.push_back(transitions[k]);
+ goto LOOP;
+ }
+ }
+ }
+ LOOP:;
+ }
+
+ enabledTransitions = filterPreempted(enabledTransitions);
+ return enabledTransitions;
+}
+
+bool Interpreter::hasConditionMatch(const Arabica::DOM::Node<std::string>& conditional) {
+ try {
+ if (_dataModel && HAS_ATTR(conditional, "cond"))
+ return _dataModel->evalAsBool(ATTR(conditional, "cond"));
+ } catch (Event e) {
+ LOG(ERROR) << "Syntax error in cond attribute of " << TAGNAME(conditional) << " element:" << std::endl << e << std::endl;
+ return false;
+ }
+ return true; // no condition is always true
+}
+
+Arabica::XPath::NodeSet<std::string> Interpreter::filterPreempted(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) {
+ Arabica::XPath::NodeSet<std::string> filteredTransitions;
+ for (unsigned int i = 0; i < enabledTransitions.size(); i++) {
+ Arabica::DOM::Node<std::string> t = enabledTransitions[i];
+ for (unsigned int j = i+1; j < enabledTransitions.size(); j++) {
+ Arabica::DOM::Node<std::string> t2 = enabledTransitions[j];
+ if (isPreemptingTransition(t2, t))
+ goto LOOP;
+ }
+ filteredTransitions.push_back(t);
+ LOOP:;
+ }
+ return filteredTransitions;
+}
+
+bool Interpreter::isPreemptingTransition(const Arabica::DOM::Node<std::string>& t1, const Arabica::DOM::Node<std::string>& t2) {
+ if (t1 == t2)
+ return false;
+ if (isWithinSameChild(t1) && (!isTargetless(t2) && !isWithinSameChild(t2)))
+ return true;
+ if (!isTargetless(t1) && !isWithinSameChild(t1))
+ return true;
+ return false;
+}
+
+void Interpreter::microstep(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) {
+#if 0
+ std::cout << "Transitions: ";
+ for (int i = 0; i < enabledTransitions.size(); i++) {
+ std::cout << ((Arabica::DOM::Element<std::string>)getSourceState(enabledTransitions[i])).getAttribute("id") << " -> " << std::endl;
+ NodeSet<std::string> targetSet = getTargetStates(enabledTransitions[i]);
+ for (int j = 0; j < targetSet.size(); j++) {
+ std::cout << " " << ((Arabica::DOM::Element<std::string>)targetSet[i]).getAttribute("id") << std::endl;
+ }
+ }
+ std::cout << std::endl;
+#endif
+
+ exitStates(enabledTransitions);
+ executeTransitionContent(enabledTransitions);
+ enterStates(enabledTransitions);
+}
+
+void Interpreter::exitInterpreter() {
+ NodeSet<std::string> statesToExit = _configuration;
+ statesToExit.to_document_order();
+ statesToExit.reverse();
+
+ for (int i = 0; i < statesToExit.size(); i++) {
+ Arabica::XPath::NodeSet<std::string> onExitElems = _xpath.evaluate("" + _nsPrefix + "onexit", statesToExit[i]).asNodeSet();
+ for (int j = 0; j < onExitElems.size(); j++) {
+ executeContent(onExitElems[j]);
+ }
+ Arabica::XPath::NodeSet<std::string> invokeElems = _xpath.evaluate("" + _nsPrefix + "invoke", statesToExit[i]).asNodeSet();
+ for (int j = 0; j < invokeElems.size(); j++) {
+ cancelInvoke(invokeElems[j]);
+ }
+ if (isFinal(statesToExit[i]) && parentIsScxmlState(statesToExit[i])) {
+ returnDoneEvent(statesToExit[i]);
+ }
+ }
+ _configuration = NodeSet<std::string>();
+}
+
+void Interpreter::executeTransitionContent(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) {
+ for (int i = 0; i < enabledTransitions.size(); i++) {
+ executeContent(enabledTransitions[i]);
+ }
+}
+
+void Interpreter::executeContent(const NodeList<std::string>& content) {
+ for (unsigned int i = 0; i < content.getLength(); i++) {
+ if (content.item(i).getNodeType() != Node_base::ELEMENT_NODE)
+ continue;
+ executeContent(content.item(i));
+ }
+}
+
+void Interpreter::executeContent(const Arabica::DOM::Node<std::string>& content) {
+ if (content.getNodeType() != Node_base::ELEMENT_NODE)
+ return;
+
+ if (false) {
+ } else if (boost::iequals(TAGNAME(content), "raise")) {
+ // --- RAISE --------------------------
+ if (HAS_ATTR(content, "event")) {
+ Event event;
+ event.name = ATTR(content, "event");
+ _internalQueue.push_back(event);
+ }
+ } else if (boost::iequals(TAGNAME(content), "if")) {
+ // --- IF / ELSEIF / ELSE --------------
+ Arabica::DOM::Element<std::string> ifElem = (Arabica::DOM::Element<std::string>)content;
+ if(hasConditionMatch(ifElem)) {
+ // condition is true, execute all content up to an elseif, else or end
+ if (ifElem.hasChildNodes()) {
+ NodeList<std::string> childs = ifElem.getChildNodes();
+ for (unsigned int i = 0; i < childs.getLength(); i++) {
+ if (childs.item(i).getNodeType() != Node_base::ELEMENT_NODE)
+ continue;
+ if (boost::iequals(TAGNAME(childs.item(i)), "elsif") ||
+ boost::iequals(TAGNAME(childs.item(i)), "else"))
+ break;
+ executeContent(childs.item(i));
+ }
+ }
+ } else {
+ // condition does not match - do we have an elsif?
+ if (ifElem.hasChildNodes()) {
+ NodeList<std::string> elseifElem = ifElem.getElementsByTagName("elseif");
+ for (unsigned int i = 0; i < elseifElem.getLength(); i++) {
+ if (hasConditionMatch(elseifElem.item(i))) {
+ executeContent(elseifElem.item(i).getChildNodes());
+ goto ELSIF_ELEM_MATCH;
+ }
+ }
+ NodeList<std::string> elseElem = ifElem.getElementsByTagName("else");
+ if (elseElem.getLength() > 0)
+ executeContent(elseElem.item(0).getChildNodes());
+ }
+ }
+ ELSIF_ELEM_MATCH:;
+ } else if (boost::iequals(TAGNAME(content), "elseif")) {
+ std::cerr << "Found single elsif to evaluate!" << std::endl;
+ } else if (boost::iequals(TAGNAME(content), "else")) {
+ std::cerr << "Found single else to evaluate!" << std::endl;
+ } else if (boost::iequals(TAGNAME(content), "foreach")) {
+ // --- FOREACH --------------------------
+ if (_dataModel) {
+ if (HAS_ATTR(content, "array") && HAS_ATTR(content, "item")) {
+ std::string array = ATTR(content, "array");
+ std::string item = ATTR(content, "item");
+ std::string index = (HAS_ATTR(content, "index") ? ATTR(content, "index") : "");
+ uint32_t iterations = _dataModel->getLength(array);
+ _dataModel->pushContext(); // copy old and enter new context
+ for (uint32_t iteration = 0; iteration < iterations; iteration++) {
+ {
+ // assign array element to item
+ std::stringstream ss;
+ ss << array << "[" << iteration << "]";
+ _dataModel->assign(item, ss.str());
+ }
+ if (index.length() > 0) {
+ // assign iteration element to index
+ std::stringstream ss;
+ ss << iteration;
+ _dataModel->assign(index,ss.str());
+ }
+ if (content.hasChildNodes())
+ executeContent(content.getChildNodes());
+ }
+ _dataModel->popContext(); // leave stacked context
+ } else {
+ LOG(ERROR) << "Expected array and item attributes with foreach element!" << std::endl;
+ }
+ }
+ } else if (boost::iequals(TAGNAME(content), "log")) {
+ // --- LOG --------------------------
+ Arabica::DOM::Element<std::string> logElem = (Arabica::DOM::Element<std::string>)content;
+ if (logElem.hasAttribute("expr")) {
+ if (_dataModel) {
+ try {
+ std::cout << _dataModel->evalAsString(logElem.getAttribute("expr")) << std::endl;
+ } catch (Event e) {
+ LOG(ERROR) << "Syntax error in expr attribute of log element:" << std::endl << e << std::endl;
+ }
+ } else {
+ std::cout << logElem.getAttribute("expr") << std::endl;
+ }
+ }
+ } else if (boost::iequals(TAGNAME(content), "assign")) {
+ // --- ASSIGN --------------------------
+ if (_dataModel && HAS_ATTR(content, "location") && HAS_ATTR(content, "expr")) {
+ try {
+ _dataModel->assign(ATTR(content, "location"), ATTR(content, "expr"));
+ } catch (Event e) {
+ LOG(ERROR) << "Syntax error in attributes of assign element:" << std::endl << e << std::endl;
+ }
+ }
+ } else if (boost::iequals(TAGNAME(content), "validate")) {
+ // --- VALIDATE --------------------------
+ if (_dataModel) {
+ std::string location = (HAS_ATTR(content, "location") ? ATTR(content, "location") : "");
+ std::string schema = (HAS_ATTR(content, "schema") ? ATTR(content, "schema") : "");
+ _dataModel->validate(location, schema);
+ }
+ } else if (boost::iequals(TAGNAME(content), "script")) {
+ // --- SCRIPT --------------------------
+ if (_dataModel) {
+ if (HAS_ATTR(content, "src")) {
+ Arabica::SAX::InputSourceResolver resolver(Arabica::SAX::InputSource<std::string>(ATTR(content, "src")),
+ Arabica::default_string_adaptor<std::string>());
+ std::string srcContent(std::istreambuf_iterator<char>(*resolver.resolve()), std::istreambuf_iterator<char>());
+ try {
+ _dataModel->eval(srcContent);
+ } catch (Event e) {
+ LOG(ERROR) << "Syntax error while executing script element from '" << ATTR(content, "src") << "':" << std::endl << e << std::endl;
+ }
+ } else {
+ if (content.hasChildNodes()) {
+ // search for the text node with the actual script
+ if (content.getFirstChild().getNodeType() == Node_base::TEXT_NODE) {
+ try {
+ _dataModel->eval(content.getFirstChild().getNodeValue());
+ } catch (Event e) {
+ LOG(ERROR) << "Syntax error while executing script element" << std::endl << e << std::endl;
+ }
+ }
+ }
+ }
+ }
+ } else if (boost::iequals(TAGNAME(content), "send")) {
+ // --- SEND --------------------------
+ send(content);
+ } else if (boost::iequals(TAGNAME(content), "cancel")) {
+ // --- CANCEL --------------------------
+ std::string sendId;
+ try {
+ if (HAS_ATTR(content, "sendidexpr")) {
+ sendId = _dataModel->evalAsString(ATTR(content, "sendidexpr"));
+ } else if(HAS_ATTR(content, "sendid")) {
+ sendId = ATTR(content, "sendid");
+ } else {
+ LOG(ERROR) << "Expected sendidexpr or sendid attribute in cancel element";
+ return;
+ }
+
+ IOProcessor* ioProc = getIOProcessorForId(sendId);
+ if (ioProc != NULL) {
+ ioProc->cancel(sendId);
+ }
+
+ } catch (Event e) {
+ LOG(ERROR) << "Syntax error while executing cancel element" << std::endl << e << std::endl;
+ }
+
+ } else if (boost::iequals(TAGNAME(content), "invoke")) {
+ // --- INVOKE --------------------------
+ } else {
+ NodeList<std::string> executable = content.getChildNodes();
+ for (int i = 0; i < executable.getLength(); i++) {
+ executeContent(executable.item(i));
+ }
+ }
+}
+
+void Interpreter::cancelInvoke(const Arabica::DOM::Node<std::string>& content) {
+}
+
+void Interpreter::returnDoneEvent(const Arabica::DOM::Node<std::string>& state) {
+}
+
+void Interpreter::exitStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) {
+ NodeSet<std::string> statesToExit;
+ for (int i = 0; i < enabledTransitions.size(); i++) {
+ Arabica::DOM::Element<std::string> transition = ((Arabica::DOM::Element<std::string>)enabledTransitions[i]);
+ if (!isTargetless(transition)) {
+ std::string transitionType = (boost::iequals(transition.getAttribute("type"), "internal") ? "internal" : "external");
+ NodeSet<std::string> tStates = getTargetStates(transition);
+ Arabica::DOM::Node<std::string> ancestor;
+ Arabica::DOM::Node<std::string> source = getSourceState(transition);
+
+ bool allDescendants = true;
+ for (int j = 0; j < tStates.size(); j++) {
+ if (!isDescendant(tStates[j], source)) {
+ allDescendants = false;
+ break;
+ }
+ }
+ if (boost::iequals(transitionType, "internal") &&
+ isCompound(source) &&
+ allDescendants)
+ {
+ ancestor = source;
+ } else {
+ NodeSet<std::string> tmpStates;
+ tmpStates.push_back(source);
+ tmpStates.insert(tmpStates.end(), tStates.begin(), tStates.end());
+
+ ancestor = findLCCA(tmpStates);
+ }
+
+ for (int j = 0; j < _configuration.size(); j++) {
+ if (isDescendant(_configuration[j], ancestor))
+ statesToExit.push_back(_configuration[j]);
+ }
+ }
+ }
+ // remove statesToExit from _statesToInvoke
+ std::list<Arabica::DOM::Node<std::string> > tmp;
+ for (int i = 0; i < _statesToInvoke.size(); i++) {
+ if (!isMember(_statesToInvoke[i], statesToExit)) {
+ tmp.push_back(_statesToInvoke[i]);
+ }
+ }
+ _statesToInvoke = NodeSet<std::string>();
+ _statesToInvoke.insert(_statesToInvoke.end(), tmp.begin(), tmp.end());
+
+ statesToExit.to_document_order();
+ statesToExit.reverse();
+
+ for (int i = 0; i < statesToExit.size(); i++) {
+ NodeSet<std::string> historyElems = _xpath.evaluate("" + _nsPrefix + "history", statesToExit[i]).asNodeSet();
+ for (int j = 0; j < historyElems.size(); j++) {
+ Arabica::DOM::Element<std::string> historyElem = (Arabica::DOM::Element<std::string>)historyElems[j];
+ std::string historyType = (historyElem.hasAttribute("type") ? historyElem.getAttribute("type") : "shallow");
+ NodeSet<std::string> historyNodes;
+ for (int k = 0; k < _configuration.size(); k++) {
+ if (boost::iequals(historyType, "deep")) {
+ if (isAtomic(_configuration[k]) && isDescendant(_configuration[k], statesToExit[i]))
+ historyNodes.push_back(_configuration[k]);
+ } else {
+ if (_configuration[k].getParentNode() == statesToExit[i])
+ historyNodes.push_back(_configuration[k]);
+ }
+ }
+ _historyValue[historyElem.getAttribute("id")] = historyNodes;
+ }
+ }
+
+ for (int i = 0; i < statesToExit.size(); i++) {
+ Arabica::XPath::NodeSet<std::string> onExitElems = _xpath.evaluate("" + _nsPrefix + "onexit", statesToExit[i]).asNodeSet();
+ for (int j = 0; j < onExitElems.size(); j++) {
+ executeContent(onExitElems[j]);
+ }
+ Arabica::XPath::NodeSet<std::string> invokeElems = _xpath.evaluate("" + _nsPrefix + "invoke", statesToExit[i]).asNodeSet();
+ for (int j = 0; j < invokeElems.size(); j++) {
+ cancelInvoke(invokeElems[j]);
+ }
+ }
+
+// std::cout << "States to Exit: ";
+// for (int i = 0; i < statesToExit.size(); i++) {
+// std::cout << ((Arabica::DOM::Element<std::string>)statesToExit[i]).getAttribute("id") << ", ";
+// }
+// std::cout << std::endl;
+
+ // remove statesToExit from _configuration
+ tmp.clear();
+ for (int i = 0; i < _configuration.size(); i++) {
+ if (!isMember(_configuration[i], statesToExit)) {
+ tmp.push_back(_configuration[i]);
+ }
+ }
+ _configuration = NodeSet<std::string>();
+ _configuration.insert(_configuration.end(), tmp.begin(), tmp.end());
+
+
+}
+
+void Interpreter::enterStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) {
+ NodeSet<std::string> statesToEnter;
+ NodeSet<std::string> statesForDefaultEntry;
+
+ for (int i = 0; i < enabledTransitions.size(); i++) {
+ Arabica::DOM::Element<std::string> transition = ((Arabica::DOM::Element<std::string>)enabledTransitions[i]);
+ if (!isTargetless(transition)) {
+ std::string transitionType = (boost::iequals(transition.getAttribute("type"), "internal") ? "internal" : "external");
+ NodeSet<std::string> tStates = getTargetStates(transition);
+ Arabica::DOM::Node<std::string> ancestor;
+ Arabica::DOM::Node<std::string> source = getSourceState(transition);
+ assert(source);
+
+ bool allDescendants = true;
+ for (int j = 0; j < tStates.size(); j++) {
+ if (!isDescendant(tStates[j], source)) {
+ allDescendants = false;
+ break;
+ }
+ }
+ if (boost::iequals(transitionType, "internal") &&
+ isCompound(source) &&
+ allDescendants)
+ {
+ ancestor = source;
+ } else {
+ NodeSet<std::string> tmpStates;
+ tmpStates.push_back(source);
+ tmpStates.insert(tmpStates.end(), tStates.begin(), tStates.end());
+
+ ancestor = findLCCA(tmpStates);
+ }
+
+ for (int j = 0; j < tStates.size(); j++) {
+ addStatesToEnter(tStates[j], statesToEnter, statesForDefaultEntry);
+ }
+
+ for (int j = 0; j < tStates.size(); j++) {
+ NodeSet<std::string> ancestors = getProperAncestors(tStates[j], ancestor);
+ for (int k = 0; k < ancestors.size(); k++) {
+ statesToEnter.push_back(ancestors[k]);
+ if(isParallel(ancestors[k])) {
+ NodeSet<std::string> childs = getChildStates(ancestors[k]);
+ for (int l = 0; l < childs.size(); l++) {
+ bool someIsDescendant = false;
+ for (int m = 0; m < statesToEnter.size(); m++) {
+ if (isDescendant(statesToEnter[m], childs[l])) {
+ someIsDescendant = true;
+ break;
+ }
+ }
+ if (!someIsDescendant) {
+ addStatesToEnter(childs[l], statesToEnter, statesForDefaultEntry);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ statesToEnter.to_document_order();
+ for (int i = 0; i < statesToEnter.size(); i++) {
+ Arabica::DOM::Element<std::string> stateElem = (Arabica::DOM::Element<std::string>)statesToEnter[i];
+ _configuration.push_back(stateElem);
+ _statesToInvoke.push_back(stateElem);
+ if (_binding == LATE && stateElem.getAttribute("isFirstEntry").size() > 0) {
+ Arabica::XPath::NodeSet<std::string> dataModelElems = _xpath.evaluate("" + _nsPrefix + "datamodel", stateElem).asNodeSet();
+ if(dataModelElems.size() > 0 && _dataModel) {
+ Arabica::XPath::NodeSet<std::string> dataElems = _xpath.evaluate("" + _nsPrefix + "data", dataModelElems[0]).asNodeSet();
+ for (int j = 0; j < dataElems.size(); j++) {
+ initializeData(dataElems[j]);
+ }
+ }
+ stateElem.setAttribute("isFirstEntry", "");
+ }
+ // execute onentry executable content
+ Arabica::XPath::NodeSet<std::string> onEntryElems = _xpath.evaluate("" + _nsPrefix + "onentry", stateElem).asNodeSet();
+ for (int j = 0; j < onEntryElems.size(); j++) {
+ executeContent(onEntryElems[j]);
+ }
+ if (isMember(stateElem, statesForDefaultEntry)) {
+ // execute initial transition content for compund states
+ Arabica::XPath::NodeSet<std::string> transitions = _xpath.evaluate("" + _nsPrefix + "initial/" + _nsPrefix + "transition", stateElem).asNodeSet();
+ for (int j = 0; j < transitions.size(); j++) {
+ executeContent(transitions[j]);
+ }
+ }
+
+ if (isFinal(stateElem)) {
+ Arabica::DOM::Element<std::string> parent = (Arabica::DOM::Element<std::string>)stateElem.getParentNode();
+
+ Event event;
+ event.name = "done.state." + parent.getAttribute("id");
+ Arabica::XPath::NodeSet<std::string> doneData = _xpath.evaluate("" + _nsPrefix + "donedata", stateElem).asNodeSet();
+ if (doneData.size() > 0) {
+ event.dom = doneData[0];
+ }
+ _internalQueue.push_back(event);
+
+ if (isParallel(parent.getParentNode())) {
+ Arabica::DOM::Element<std::string> grandParent = (Arabica::DOM::Element<std::string>)parent.getParentNode();
+
+ Arabica::XPath::NodeSet<std::string> childs = getChildStates(grandParent);
+ bool inFinalState = true;
+ for (int j = 0; j < childs.size(); j++) {
+ if (!isInFinalState(childs[j])) {
+ inFinalState = false;
+ break;
+ }
+ }
+ if (inFinalState) {
+ Event event;
+ event.name = "done.state." + grandParent.getAttribute("id");
+ _internalQueue.push_back(event);
+ }
+ }
+ }
+ }
+ for (int i = 0; i < _configuration.size(); i++) {
+ Arabica::DOM::Element<std::string> stateElem = (Arabica::DOM::Element<std::string>)_configuration[i];
+ if (isFinal(stateElem) && parentIsScxmlState(stateElem))
+ _running = false;
+ }
+}
+
+bool Interpreter::parentIsScxmlState(Arabica::DOM::Node<std::string> state) {
+ Arabica::DOM::Element<std::string> stateElem = (Arabica::DOM::Element<std::string>)state;
+ Arabica::DOM::Element<std::string> parentElem = (Arabica::DOM::Element<std::string>)state.getParentNode();
+ if (boost::iequals(parentElem.getTagName(), "scxml"))
+ return true;
+ return false;
+}
+
+bool Interpreter::isInFinalState(const Arabica::DOM::Node<std::string>& state) {
+ if (isCompound(state)) {
+ Arabica::XPath::NodeSet<std::string> childs = getChildStates(state);
+ for (int i = 0; i < childs.size(); i++) {
+ if (isFinal(childs[i]) && isMember(childs[i], _configuration))
+ return true;
+ }
+ } else if (isParallel(state)) {
+ Arabica::XPath::NodeSet<std::string> childs = getChildStates(state);
+ for (int i = 0; i < childs.size(); i++) {
+ if (!isInFinalState(childs[i]))
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool Interpreter::isMember(const Arabica::DOM::Node<std::string>& node, const Arabica::XPath::NodeSet<std::string>& set) {
+ for (int i = 0; i < set.size(); i++) {
+ if (set[i] == node)
+ return true;
+ }
+ return false;
+}
+
+void Interpreter::addStatesToEnter(const Arabica::DOM::Node<std::string>& state,
+ Arabica::XPath::NodeSet<std::string>& statesToEnter,
+ Arabica::XPath::NodeSet<std::string>& statesForDefaultEntry) {
+ std::string stateId = ((Arabica::DOM::Element<std::string>)state).getAttribute("id");
+ if (isHistory(state)) {
+ if (_historyValue.find(stateId) != _historyValue.end()) {
+ Arabica::XPath::NodeSet<std::string> historyValue = _historyValue[stateId];
+ for (int i = 0; i < historyValue.size(); i++) {
+ addStatesToEnter(historyValue[i], statesToEnter, statesForDefaultEntry);
+ NodeSet<std::string> ancestors = getProperAncestors(historyValue[i], state);
+ for (int j = 0; j < ancestors.size(); j++) {
+ statesToEnter.push_back(ancestors[j]);
+ }
+ }
+ } else {
+ NodeSet<std::string> transitions = _xpath.evaluate("" + _nsPrefix + "transition", state).asNodeSet();
+ for (int i = 0; i < transitions.size(); i++) {
+ NodeSet<std::string> targets = getTargetStates(transitions[i]);
+ for (int j = 0; j < targets.size(); j++) {
+ addStatesToEnter(targets[j], statesToEnter, statesForDefaultEntry);
+ }
+ }
+ }
+ } else {
+ statesToEnter.push_back(state);
+ if (isCompound(state)) {
+ statesForDefaultEntry.push_back(state);
+ NodeSet<std::string> tStates = getTargetStates(getInitialState(state));
+ for (int i = 0; i < tStates.size(); i++) {
+ addStatesToEnter(tStates[i], statesToEnter, statesForDefaultEntry);
+ }
+ } else if(isParallel(state)) {
+ NodeSet<std::string> childStates = getChildStates(state);
+ for (int i = 0; i < childStates.size(); i++) {
+ addStatesToEnter(childStates[i], statesToEnter, statesForDefaultEntry);
+ }
+ }
+ }
+}
+
+Arabica::XPath::NodeSet<std::string> Interpreter::getChildStates(const Arabica::DOM::Node<std::string>& state) {
+ Arabica::XPath::NodeSet<std::string> childs;
+ Arabica::XPath::NodeSet<std::string> stateChilds = _xpath.evaluate("" + _nsPrefix + "state", state).asNodeSet();
+ Arabica::XPath::NodeSet<std::string> parallelChilds = _xpath.evaluate("" + _nsPrefix + "parallel", state).asNodeSet();
+
+ childs.insert(childs.begin(), stateChilds.begin(), stateChilds.end());
+ childs.insert(childs.begin(), parallelChilds.begin(), parallelChilds.end());
+
+ return childs;
+}
+
+Arabica::DOM::Node<std::string> Interpreter::findLCCA(const Arabica::XPath::NodeSet<std::string>& states) {
+// std::cout << "findLCCA: ";
+// for (int i = 0; i < states.size(); i++) {
+// std::cout << ((Arabica::DOM::Element<std::string>)states[i]).getAttribute("id") << " - " << states[i].getLocalName() << ", ";
+// }
+// std::cout << std::flush;
+
+ Arabica::XPath::NodeSet<std::string> ancestors = getProperAncestors(states[0], Arabica::DOM::Node<std::string>());
+ ancestors.push_back(states[0]); // state[0] may already be the ancestor - bug in W3C spec?
+ Arabica::DOM::Node<std::string> ancestor;
+ for (int i = 0; i < ancestors.size(); i++) {
+ for (int j = 0; j < states.size(); j++) {
+// std::cout << "Checking " << TAGNAME(state) << " and " << TAGNAME(ancestors[i]) << std::endl;
+ if (!isDescendant(states[j], ancestors[i]) && (states[j] != ancestors[i]))
+ goto NEXT_ANCESTOR;
+ }
+ ancestor = ancestors[i];
+ break;
+ NEXT_ANCESTOR:;
+ }
+ assert(ancestor);
+// std::cout << " -> " << ((Arabica::DOM::Element<std::string>)ancestor).getAttribute("id") << " " << ancestor.getLocalName() << std::endl;
+ return ancestor;
+}
+
+Arabica::DOM::Node<std::string> Interpreter::getState(const std::string& stateId) {
+ // first try atomic and compund states
+ NodeSet<std::string> target = _xpath.evaluate("//" + _nsPrefix + "state[@id='" + stateId + "']", _doc).asNodeSet();
+ if (target.size() > 0)
+ goto FOUND;
+
+ // now parallel states
+ target = _xpath.evaluate("//" + _nsPrefix + "parallel[@id='" + stateId + "']", _doc).asNodeSet();
+ if (target.size() > 0)
+ goto FOUND;
+
+ // now final states
+ target = _xpath.evaluate("//" + _nsPrefix + "final[@id='" + stateId + "']", _doc).asNodeSet();
+ if (target.size() > 0)
+ goto FOUND;
+
+FOUND:
+ if (target.size() > 0) {
+ assert(target.size() == 1);
+ return target[0];
+ }
+ // return the empty node
+ return Arabica::DOM::Node<std::string>();
+}
+
+Arabica::DOM::Node<std::string> Interpreter::getSourceState(const Arabica::DOM::Node<std::string>& transition) {
+ if (boost::iequals(TAGNAME(transition.getParentNode()), "initial"))
+ return transition.getParentNode().getParentNode();
+ return transition.getParentNode();
+}
+
+ /**
+ * In a conformant SCXML document, a compound state may specify either an "initial"
+ * attribute or an <initial> element, but not both. See 3.6 <initial> for a
+ * discussion of the difference between the two notations. If neither the "initial"
+ * attribute nor an <initial> element is specified, the SCXML Processor must use
+ * the first child state in document order as the default initial state.
+ */
+Arabica::DOM::Node<std::string> Interpreter::getInitialState(Arabica::DOM::Node<std::string> state) {
+ if (!state) {
+ state = _doc.getFirstChild();
+ while(!isState(state))
+ state = state.getNextSibling();
+ }
+
+ assert(isCompound(state) || isParallel(state));
+
+ // initial attribute at element
+ Arabica::DOM::Element<std::string> stateElem = (Arabica::DOM::Element<std::string>)state;
+ if (stateElem.hasAttribute("initial")) {
+ return getState(stateElem.getAttribute("initial"));
+ }
+
+ // initial element as child
+ NodeSet<std::string> initialStates = _xpath.evaluate("" + _nsPrefix + "initial", state).asNodeSet();
+ if(initialStates.size() == 1)
+ return initialStates[0];
+
+ // first child state
+ NodeList<std::string> childs = state.getChildNodes();
+ for (int i = 0; i < childs.getLength(); i++) {
+ if (isState(childs.item(i)))
+ return childs.item(i);
+ }
+ // nothing found
+ return Arabica::DOM::Node<std::string>();
+}
+
+NodeSet<std::string> Interpreter::getTargetStates(const Arabica::DOM::Node<std::string>& transition) {
+ NodeSet<std::string> targetStates;
+ std::string targetId = ((Arabica::DOM::Element<std::string>)transition).getAttribute("target");
+
+ std::vector<std::string> targetIds = Interpreter::tokenizeIdRefs(ATTR(transition, "target"));
+ for (int i = 0; i < targetIds.size(); i++) {
+ targetStates.push_back(getState(targetIds[i]));
+ }
+ return targetStates;
+}
+
+std::vector<std::string> Interpreter::tokenizeIdRefs(const std::string& idRefs) {
+ std::vector<std::string> ids;
+
+ if (idRefs.length() > 0) {
+ std::istringstream iss(idRefs);
+
+ std::copy(std::istream_iterator<std::string>(iss),
+ std::istream_iterator<std::string>(),
+ std::back_inserter<std::vector<std::string> >(ids));
+ }
+
+ return ids;
+}
+
+NodeSet<std::string> Interpreter::getProperAncestors(const Arabica::DOM::Node<std::string>& s1,
+ const Arabica::DOM::Node<std::string>& s2) {
+ NodeSet<std::string> ancestors;
+ if (isState(s1)) {
+ Arabica::DOM::Node<std::string> node = s1;
+ while((node = node.getParentNode())) {
+ if (!isState(node))
+ break;
+ if (!boost::iequals(TAGNAME(node), "parallel") && !boost::iequals(TAGNAME(node), "state") && !boost::iequals(TAGNAME(node), "scxml"))
+ break;
+ if (node == s2)
+ break;
+ ancestors.push_back(node);
+ }
+ }
+ return ancestors;
+}
+
+bool Interpreter::isDescendant(const Arabica::DOM::Node<std::string>& s1,
+ const Arabica::DOM::Node<std::string>& s2) {
+ Arabica::DOM::Node<std::string> parent = s1.getParentNode();
+ while(parent) {
+ if (s2 == parent)
+ return true;
+ parent = parent.getParentNode();
+ }
+ return false;
+}
+
+bool Interpreter::isTargetless(const Arabica::DOM::Node<std::string>& transition) {
+ if (transition.hasAttributes()) {
+ if (((Arabica::DOM::Element<std::string>)transition).hasAttribute("target"))
+ return false;
+ }
+ return true;
+}
+
+bool Interpreter::isWithinSameChild(const Arabica::DOM::Node<std::string>& transition) {
+ if (!isTargetless(transition)) {
+ std::string target = ((Arabica::DOM::Element<std::string>)transition).getAttribute("target");
+ Arabica::XPath::XPath<std::string> xpath;
+ // @todo: do we need to look at parallel as well?
+ if (xpath.evaluate("" + _nsPrefix + "state[id=\"" + target + "\"]", transition.getParentNode()).asNodeSet().size() > 0)
+ return true;
+ }
+ return false;
+}
+
+bool Interpreter::isState(const Arabica::DOM::Node<std::string>& state) {
+ if (!state)
+ return false;
+ if (state.getNodeType() != Arabica::DOM::Node_base::ELEMENT_NODE)
+ return false;
+
+ std::string tagName = TAGNAME(state);
+ if (boost::iequals("state", tagName))
+ return true;
+ if (boost::iequals("scxml", tagName))
+ return true;
+ if (boost::iequals("parallel", tagName))
+ return true;
+ if (boost::iequals("final", tagName))
+ return true;
+ return false;
+}
+
+bool Interpreter::isFinal(const Arabica::DOM::Node<std::string>& state) {
+ std::string tagName = TAGNAME(state);
+ if (boost::iequals("final", tagName))
+ return true;
+ if (HAS_ATTR(state, "final") && boost::iequals("true", ATTR(state, "final")))
+ return true;
+ return false;
+}
+
+bool Interpreter::isPseudoState(const Arabica::DOM::Node<std::string>& state) {
+ std::string tagName = TAGNAME(state);
+ if (boost::iequals("initial", tagName))
+ return true;
+ if (boost::iequals("history", tagName))
+ return true;
+ return false;
+}
+
+bool Interpreter::isTransitionTarget(const Arabica::DOM::Node<std::string>& elem) {
+ return (isState(elem) || boost::iequals(TAGNAME(elem), "history"));
+}
+
+bool Interpreter::isAtomic(const Arabica::DOM::Node<std::string>& state) {
+ if (boost::iequals("final", TAGNAME(state)))
+ return true;
+
+ // I will assume that parallel states are not meant to be atomic.
+ if (boost::iequals("parallel", TAGNAME(state)))
+ return false;
+
+ Arabica::DOM::NodeList<std::string> childs = state.getChildNodes();
+ for (unsigned int i = 0; i < childs.getLength(); i++) {
+ if (isState(childs.item(i)))
+ return false;
+ }
+ return true;
+}
+
+bool Interpreter::isHistory(const Arabica::DOM::Node<std::string>& state) {
+ if (boost::iequals("history", TAGNAME(state)))
+ return true;
+ return false;
+}
+
+bool Interpreter::isParallel(const Arabica::DOM::Node<std::string>& state) {
+ if (!isState(state))
+ return false;
+ if (boost::iequals("parallel", TAGNAME(state)))
+ return true;
+ return false;
+}
+
+
+bool Interpreter::isCompound(const Arabica::DOM::Node<std::string>& state) {
+ if (!isState(state))
+ return false;
+
+ if (boost::iequals(TAGNAME(state), "parallel"))
+ return false;
+
+ Arabica::DOM::NodeList<std::string> childs = state.getChildNodes();
+ for (unsigned int i = 0; i < childs.getLength(); i++) {
+ if (isState(childs.item(i)))
+ return true;
+ }
+ return false;
+}
+
+void Interpreter::setupIOProcessors() {
+ std::map<std::string, IOProcessor*>::iterator ioProcIter = Factory::getInstance()->_ioProcessors.begin();
+ while(ioProcIter != Factory::getInstance()->_ioProcessors.end()) {
+ _ioProcessors[ioProcIter->first] = Factory::getIOProcessor(ioProcIter->first, this);
+ if (_dataModel) {
+ try {
+ _dataModel->assign("_ioprocessors['" + ioProcIter->first + "']", "'" + _ioProcessors[ioProcIter->first]->getURL() + "'");
+ } catch (Event e) {
+ LOG(ERROR) << "Syntax error when setting _ioprocessors:" << std::endl << e << std::endl;
+ }
+ } else {
+ LOG(INFO) << "Not registering " << ioProcIter->first << " at _ioprocessors in datamodel, no datamodel specified";
+ }
+ ioProcIter++;
+ }
+}
+
+IOProcessor* Interpreter::getIOProcessor(const std::string& type) {
+ if (_ioProcessors.find(type) == _ioProcessors.end()) {
+ LOG(ERROR) << "No ioProcessor known for type " << type;
+ return NULL;
+ }
+ return _ioProcessors[type];
+}
+
+IOProcessor* Interpreter::getIOProcessorForId(const std::string& sendId) {
+ if (_ioProcessorsIds.find(sendId) == _ioProcessorsIds.end()) {
+ LOG(ERROR) << "No ioProcessor with pending send id " << sendId << sendId;
+ return NULL;
+ }
+ return _ioProcessorsIds[sendId];
+}
+
+bool Interpreter::validate() {
+ bool validationErrors = false;
+
+ if (!_doc) {
+ LOG(ERROR) << "Document " << _url << " was not parsed successfully" << std::endl;
+ return false;
+ }
+
+ // semantic issues ------------
+ if ((_xpath.evaluate("/" + _nsPrefix + "scxml/@datamodel", _doc).asNodeSet().size() == 0) &&
+ _xpath.evaluate("/" + _nsPrefix + "scxml/" + _nsPrefix + "script", _doc).asNodeSet().size() > 0) {
+ LOG(ERROR) << "Script elements used, but no datamodel specified" << std::endl;
+ }
+
+ // element issues ------------
+ Arabica::XPath::NodeSet<std::string> scxmlElems = _xpath.evaluate(_nsPrefix + "scxml", _doc).asNodeSet();
+ if (scxmlElems.size() > 0)
+ LOG(ERROR) << "More than one scxml element found" << std::endl;
+ for (unsigned int i = 0; i < scxmlElems.size(); i++) {
+ if (!HAS_ATTR(scxmlElems[i], "xmlns"))
+ LOG(ERROR) << "scxml element has no xmlns attribute" << std::endl;
+ if (!HAS_ATTR(scxmlElems[i], "version"))
+ LOG(ERROR) << "scxml element has no version attribute" << std::endl;
+ NodeList<std::string> childs = scxmlElems[i].getChildNodes();
+ for (unsigned int j = 0; j < childs.getLength(); j++) {
+ if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE)
+ continue;
+ if (boost::iequals(TAGNAME(childs.item(j)), "state") ||
+ boost::iequals(TAGNAME(childs.item(j)), "parallel") ||
+ boost::iequals(TAGNAME(childs.item(j)), "final") ||
+ boost::iequals(TAGNAME(childs.item(j)), "datamodel") ||
+ boost::iequals(TAGNAME(childs.item(j)), "script")) {
+ LOG(ERROR) << "scxml element has unspecified child " << TAGNAME(childs.item(j)) << std::endl;
+ }
+ }
+ }
+
+ Arabica::XPath::NodeSet<std::string> stateElems = _xpath.evaluate(_nsPrefix + "state", _doc).asNodeSet();
+ for (unsigned int i = 0; i < stateElems.size(); i++) {
+ NodeList<std::string> childs = stateElems[i].getChildNodes();
+ for (unsigned int j = 0; j < childs.getLength(); j++) {
+ if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE)
+ continue;
+ if (boost::iequals(TAGNAME(childs.item(j)), "onentry") ||
+ boost::iequals(TAGNAME(childs.item(j)), "onexit") ||
+ boost::iequals(TAGNAME(childs.item(j)), "transition") ||
+ boost::iequals(TAGNAME(childs.item(j)), "initial") ||
+ boost::iequals(TAGNAME(childs.item(j)), "state") ||
+ boost::iequals(TAGNAME(childs.item(j)), "parallel") ||
+ boost::iequals(TAGNAME(childs.item(j)), "final") ||
+ boost::iequals(TAGNAME(childs.item(j)), "history") ||
+ boost::iequals(TAGNAME(childs.item(j)), "datamodel") ||
+ boost::iequals(TAGNAME(childs.item(j)), "invoke")) {
+ LOG(ERROR) << "state element has unspecified child " << TAGNAME(childs.item(j)) << std::endl;
+ }
+ }
+ }
+
+ Arabica::XPath::NodeSet<std::string> parallelElems = _xpath.evaluate(_nsPrefix + "parallel", _doc).asNodeSet();
+ for (unsigned int i = 0; i < parallelElems.size(); i++) {
+ NodeList<std::string> childs = parallelElems[i].getChildNodes();
+ for (unsigned int j = 0; j < childs.getLength(); j++) {
+ if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE)
+ continue;
+ if (boost::iequals(TAGNAME(childs.item(j)), "onentry") ||
+ boost::iequals(TAGNAME(childs.item(j)), "onexit") ||
+ boost::iequals(TAGNAME(childs.item(j)), "transition") ||
+ boost::iequals(TAGNAME(childs.item(j)), "state") ||
+ boost::iequals(TAGNAME(childs.item(j)), "parallel") ||
+ boost::iequals(TAGNAME(childs.item(j)), "history") ||
+ boost::iequals(TAGNAME(childs.item(j)), "datamodel") ||
+ boost::iequals(TAGNAME(childs.item(j)), "invoke")) {
+ LOG(ERROR) << "parallel element has unspecified child " << TAGNAME(childs.item(j)) << std::endl;
+ }
+ }
+ }
+
+ Arabica::XPath::NodeSet<std::string> transitionElems = _xpath.evaluate(_nsPrefix + "transition", _doc).asNodeSet();
+ for (unsigned int i = 0; i < transitionElems.size(); i++) {
+ if (HAS_ATTR(transitionElems[i], "cond") &&
+ !HAS_ATTR(transitionElems[i], "event")) {
+ LOG(ERROR) << "transition element with cond attribute but without event attribute not allowed" << std::endl;
+ }
+ NodeList<std::string> childs = scxmlElems[i].getChildNodes();
+ for (unsigned int j = 0; j < childs.getLength(); j++) {
+ if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE)
+ continue;
+ }
+ }
+
+ Arabica::XPath::NodeSet<std::string> initialElems = _xpath.evaluate(_nsPrefix + "initial", _doc).asNodeSet();
+ for (unsigned int i = 0; i < initialElems.size(); i++) {
+ NodeList<std::string> childs = initialElems[i].getChildNodes();
+ for (unsigned int j = 0; j < childs.getLength(); j++) {
+ if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE)
+ continue;
+ if (boost::iequals(TAGNAME(childs.item(j)), "transition")) {
+ LOG(ERROR) << "initial element has unspecified child " << TAGNAME(childs.item(j)) << std::endl;
+ }
+ }
+ }
+
+ Arabica::XPath::NodeSet<std::string> finalElems = _xpath.evaluate(_nsPrefix + "final", _doc).asNodeSet();
+ for (unsigned int i = 0; i < finalElems.size(); i++) {
+ NodeList<std::string> childs = finalElems[i].getChildNodes();
+ for (unsigned int j = 0; j < childs.getLength(); j++) {
+ if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE)
+ continue;
+ if (!boost::iequals(TAGNAME(childs.item(j)), "onentry") ||
+ !boost::iequals(TAGNAME(childs.item(j)), "onexit") ||
+ !boost::iequals(TAGNAME(childs.item(j)), "donedata")) {
+ LOG(ERROR) << "parallel element has unspecified child " << TAGNAME(childs.item(j)) << std::endl;
+ }
+ }
+ }
+
+ Arabica::XPath::NodeSet<std::string> historyElems = _xpath.evaluate(_nsPrefix + "history", _doc).asNodeSet();
+ for (unsigned int i = 0; i < historyElems.size(); i++) {
+ NodeList<std::string> childs = historyElems[i].getChildNodes();
+ for (unsigned int j = 0; j < childs.getLength(); j++) {
+ if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE)
+ continue;
+ if (boost::iequals(TAGNAME(childs.item(j)), "transition")) {
+ LOG(ERROR) << "history element has unspecified child " << TAGNAME(childs.item(j)) << std::endl;
+ }
+ }
+ }
+
+ Arabica::XPath::NodeSet<std::string> datamodelElems = _xpath.evaluate(_nsPrefix + "datamodel", _doc).asNodeSet();
+ for (unsigned int i = 0; i < datamodelElems.size(); i++) {
+ NodeList<std::string> childs = datamodelElems[i].getChildNodes();
+ for (unsigned int j = 0; j < childs.getLength(); j++) {
+ if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE)
+ continue;
+ if (!boost::iequals(TAGNAME(childs.item(j)), "data")) {
+ LOG(ERROR) << "datamodel element has unspecified child " << TAGNAME(childs.item(j)) << std::endl;
+ }
+ }
+ }
+
+ Arabica::XPath::NodeSet<std::string> dataElems = _xpath.evaluate(_nsPrefix + "data", _doc).asNodeSet();
+ for (unsigned int i = 0; i < dataElems.size(); i++) {
+ if (!HAS_ATTR(dataElems[i], "id"))
+ LOG(ERROR) << "data element has no id attribute" << std::endl;
+ }
+
+ return validationErrors;
+}
+
+void Interpreter::dump() {
+ if (!_doc)
+ return;
+ dump(_doc);
+}
+
+void Interpreter::dump(const Arabica::DOM::Node<std::string>& node, int lvl) {
+ if (!node)
+ return;
+
+ std::string indent = "";
+ for (unsigned int i = 0; i < lvl; i++)
+ indent += " ";
+
+ std::cout << indent;
+ switch(node.getNodeType()) {
+ case Arabica::DOM::Node_base::ELEMENT_NODE: {
+ std::cout << "ELEMENT_NODE: ";
+ Arabica::DOM::Element<std::string> elem = (Arabica::DOM::Element<std::string>)node;
+ break;
+ }
+ case Arabica::DOM::Node_base::ATTRIBUTE_NODE:
+ std::cout << "ATTRIBUTE_NODE: ";
+ break;
+ case Arabica::DOM::Node_base::TEXT_NODE:
+ std::cout << "TEXT_NODE: ";
+ break;
+ case Arabica::DOM::Node_base::CDATA_SECTION_NODE:
+ std::cout << "CDATA_SECTION_NODE: ";
+ break;
+ case Arabica::DOM::Node_base::ENTITY_REFERENCE_NODE:
+ std::cout << "ENTITY_REFERENCE_NODE: ";
+ break;
+ case Arabica::DOM::Node_base::ENTITY_NODE:
+ std::cout << "ENTITY_NODE: ";
+ break;
+ case Arabica::DOM::Node_base::PROCESSING_INSTRUCTION_NODE:
+ std::cout << "PROCESSING_INSTRUCTION_NODE: ";
+ break;
+ case Arabica::DOM::Node_base::COMMENT_NODE:
+ std::cout << "COMMENT_NODE: ";
+ break;
+ case Arabica::DOM::Node_base::DOCUMENT_NODE:
+ std::cout << "DOCUMENT_NODE: ";
+ break;
+ case Arabica::DOM::Node_base::DOCUMENT_TYPE_NODE:
+ std::cout << "DOCUMENT_TYPE_NODE: ";
+ break;
+ case Arabica::DOM::Node_base::DOCUMENT_FRAGMENT_NODE:
+ std::cout << "DOCUMENT_FRAGMENT_NODE: ";
+ break;
+ case Arabica::DOM::Node_base::NOTATION_NODE:
+ std::cout << "NOTATION_NODE: ";
+ break;
+ case Arabica::DOM::Node_base::MAX_TYPE:
+ std::cout << "MAX_TYPE: ";
+ break;
+ }
+ std::cout << node.getNamespaceURI() << " " << node.getNodeName() << std::endl;
+
+ if (node.getNodeValue().length() > 0 && node.getNodeValue().find_first_not_of(" \t\n") != std::string::npos)
+ std::cout << indent << "Value: '" << node.getNodeValue() << "'" << std::endl;
+
+
+ if (node.hasAttributes()) {
+ Arabica::DOM::NamedNodeMap<std::string> attrs = node.getAttributes();
+ for (unsigned int i = 0; i < attrs.getLength(); i++) {
+ std::cout << indent << " " << attrs.item(i).getLocalName() << " = " << attrs.item(i).getNodeValue() << " (" << std::endl;
+ std::cout << indent << " namespace: " << attrs.item(i).getNamespaceURI() << std::endl;
+ std::cout << indent << " nodeName: " << attrs.item(i).getNodeName() << std::endl;
+ std::cout << indent << " prefix: " << attrs.item(i).getPrefix() << std::endl;
+ std::cout << indent << " )" << std::endl;
+ }
+ }
+
+ if (node.hasChildNodes()) {
+ Arabica::DOM::NodeList<std::string> childs = node.getChildNodes();
+ for (unsigned int i = 0; i < childs.getLength(); i++) {
+ dump(childs.item(i), lvl+1);
+ }
+ }
+}
+
+} \ No newline at end of file
diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h
new file mode 100644
index 0000000..fb913d1
--- /dev/null
+++ b/src/uscxml/Interpreter.h
@@ -0,0 +1,314 @@
+#ifndef RUNTIME_H_SQ1MBKGN
+#define RUNTIME_H_SQ1MBKGN
+
+#include <boost/uuid/uuid_generators.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <iostream>
+#include <set>
+#include <map>
+
+#include <XPath/XPath.hpp>
+#include <DOM/Document.hpp>
+
+#include <DOM/SAX2DOM/SAX2DOM.hpp>
+#include <SAX/helpers/CatchErrorHandler.hpp>
+
+#include "uscxml/concurrency/tinythread.h"
+#include "uscxml/concurrency/BlockingQueue.h"
+#include "uscxml/Message.h"
+#include "uscxml/Factory.h"
+
+namespace uscxml {
+
+ class Interpreter {
+ public:
+ enum Binding {
+ EARLY = 0,
+ LATE = 1
+ };
+
+ Interpreter(const std::string& url);
+ virtual ~Interpreter();
+
+ void start();
+ void stop();
+ static void run(void*);
+
+ void interpret();
+ bool validate();
+
+ void waitForStabilization();
+
+ void receive(Event& event) { _externalQueue.push(event); }
+ void receiveInternal(Event& event) { _internalQueue.push_back(event); }
+ Arabica::XPath::NodeSet<std::string> getConfiguration() { return _configuration; }
+ Arabica::DOM::Node<std::string> getState(const std::string& stateId);
+
+ const std::string& getName() { return _name; }
+ const std::string& getSessionId() { return _sessionId; }
+
+ static bool isMember(const Arabica::DOM::Node<std::string>& node, const Arabica::XPath::NodeSet<std::string>& set);
+
+ void dump();
+ static void dump(const Arabica::DOM::Node<std::string>& node, int lvl = 0);
+
+ protected:
+ void normalize(const Arabica::DOM::Node<std::string>& node);
+ void setupIOProcessors();
+
+ void mainEventLoop();
+
+ bool _stable;
+ tthread::thread* _thread;
+ tthread::mutex _mutex;
+ tthread::condition_variable _stabilized;
+
+ std::string _url;
+ Arabica::DOM::Document<std::string> _doc;
+ Arabica::DOM::Element<std::string> _scxml;
+ Arabica::XPath::XPath<std::string> _xpath;
+ Arabica::XPath::StandardNamespaceContext<std::string> _nsContext;
+ std::string _nsPrefix;
+
+ bool _running;
+ Binding _binding;
+ Arabica::XPath::NodeSet<std::string> _configuration;
+ Arabica::XPath::NodeSet<std::string> _statesToInvoke;
+
+ DataModel* _dataModel;
+ std::map<std::string, Arabica::XPath::NodeSet<std::string> > _historyValue;
+
+ std::list<Event > _internalQueue;
+ uscxml::concurrency::BlockingQueue<Event> _externalQueue;
+
+ void microstep(const Arabica::XPath::NodeSet<std::string>& enabledTransitions);
+ void exitStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions);
+ void enterStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions);
+ void executeTransitionContent(const Arabica::XPath::NodeSet<std::string>& enabledTransitions);
+ void executeContent(const Arabica::DOM::Node<std::string>& content);
+ void executeContent(const Arabica::DOM::NodeList<std::string>& content);
+ void initializeData(const Arabica::DOM::Node<std::string>& data);
+ void exitInterpreter();
+
+ void addStatesToEnter(const Arabica::DOM::Node<std::string>& state,
+ Arabica::XPath::NodeSet<std::string>& statesToEnter,
+ Arabica::XPath::NodeSet<std::string>& statesForDefaultEntry);
+
+ Arabica::XPath::NodeSet<std::string> selectEventlessTransitions();
+ Arabica::XPath::NodeSet<std::string> selectTransitions(const std::string& event);
+ Arabica::XPath::NodeSet<std::string> getTargetStates(const Arabica::DOM::Node<std::string>& transition);
+ Arabica::XPath::NodeSet<std::string> getChildStates(const Arabica::DOM::Node<std::string>& state);
+ Arabica::DOM::Node<std::string> getInitialState(Arabica::DOM::Node<std::string> state = Arabica::DOM::Node<std::string>());
+ Arabica::DOM::Node<std::string> getSourceState(const Arabica::DOM::Node<std::string>& transition);
+ Arabica::DOM::Node<std::string> findLCCA(const Arabica::XPath::NodeSet<std::string>& states);
+ static Arabica::XPath::NodeSet<std::string> getProperAncestors(const Arabica::DOM::Node<std::string>& s1, const Arabica::DOM::Node<std::string>& s2);
+
+
+ void send(const std::string invokeId, Event& event);
+ void send(const Arabica::DOM::Node<std::string>& element);
+ void invoke(const Arabica::DOM::Node<std::string>& element);
+ void cancelInvoke(const Arabica::DOM::Node<std::string>& content);
+ void returnDoneEvent(const Arabica::DOM::Node<std::string>& state);
+
+ static bool nameMatch(const std::string& transitionEvent, const std::string& event);
+ Arabica::XPath::NodeSet<std::string> filterPreempted(const Arabica::XPath::NodeSet<std::string>& enabledTransitions);
+ bool hasConditionMatch(const Arabica::DOM::Node<std::string>& conditional);
+ bool isPreemptingTransition(const Arabica::DOM::Node<std::string>& t1, const Arabica::DOM::Node<std::string>& t2);
+ bool isInFinalState(const Arabica::DOM::Node<std::string>& state);
+ bool isWithinSameChild(const Arabica::DOM::Node<std::string>& transition);
+ bool parentIsScxmlState(Arabica::DOM::Node<std::string> state);
+
+ static bool isState(const Arabica::DOM::Node<std::string>& state);
+ static bool isPseudoState(const Arabica::DOM::Node<std::string>& state);
+ static bool isTransitionTarget(const Arabica::DOM::Node<std::string>& elem);
+ static bool isTargetless(const Arabica::DOM::Node<std::string>& transition);
+ static bool isAtomic(const Arabica::DOM::Node<std::string>& state);
+ static bool isFinal(const Arabica::DOM::Node<std::string>& state);
+ static bool isHistory(const Arabica::DOM::Node<std::string>& state);
+ static bool isParallel(const Arabica::DOM::Node<std::string>& state);
+ static bool isCompound(const Arabica::DOM::Node<std::string>& state);
+ static bool isDescendant(const Arabica::DOM::Node<std::string>& s1, const Arabica::DOM::Node<std::string>& s2);
+
+ static std::vector<std::string> tokenizeIdRefs(const std::string& idRefs);
+
+ static boost::uuids::random_generator uuidGen;
+ static const std::string getUUID();
+
+ std::string _name;
+ std::string _sessionId;
+
+ IOProcessor* getIOProcessor(const std::string& type);
+ IOProcessor* getIOProcessorForId(const std::string& sendId);
+
+ std::map<std::string, IOProcessor*> _ioProcessorsIds;
+ std::map<std::string, IOProcessor*> _ioProcessors;
+
+ };
+
+#if 0
+ class SCXMLParseHandler :
+ public Arabica::SAX::EntityResolver<std::string>,
+ public Arabica::SAX::DTDHandler<std::string>,
+ public Arabica::SAX::ContentHandler<std::string>,
+ public Arabica::SAX::CatchErrorHandler<std::string>,
+ public Arabica::SAX::LexicalHandler<std::string>,
+ public Arabica::SAX::DeclHandler<std::string>
+ {
+ public:
+ SCXMLParseHandler() { }
+ virtual ~SCXMLParseHandler() { }
+
+ // EntityResolver
+ virtual InputSourceT resolveEntity(const std::string& publicId , const std::string& systemId) {
+ return InputSourceT();
+ }
+
+ // DTDHandler
+ virtual void notationDecl(const std::string& name,
+ const std::string& publicId,
+ const std::string& systemId) {
+ std::cout << "notationDecl" << std::endl;
+ std::cout << " name:" << name << std::endl;
+ std::cout << " publicId:" << publicId << std::endl;
+ std::cout << " systemId:" << systemId << std::endl;
+ }
+ virtual void unparsedEntityDecl(const std::string& name,
+ const std::string& publicId,
+ const std::string& systemId,
+ const std::string& notationName) {
+ std::cout << "unparsedEntityDecl" << std::endl;
+ std::cout << " name:" << name << std::endl;
+ std::cout << " publicId:" << publicId << std::endl;
+ std::cout << " systemId:" << systemId << std::endl;
+ std::cout << " notationName:" << notationName << std::endl;
+ }
+
+ // ContentHandler
+ virtual void setDocumentLocator(const LocatorT& locator) {
+ std::cout << "setDocumentLocator" << std::endl;
+ }
+ virtual void startDocument() {
+ std::cout << "startDocument" << std::endl;
+ }
+ virtual void endDocument() {
+ std::cout << "endDocument" << std::endl;
+ }
+ virtual void startPrefixMapping(const std::string& prefix, const std::string& uri) {
+ std::cout << "startPrefixMapping" << std::endl;
+ std::cout << " prefix:" << prefix << std::endl;
+ std::cout << " uri:" << uri << std::endl;
+ }
+ virtual void endPrefixMapping(const std::string& prefix) {
+ std::cout << "endPrefixMapping" << std::endl;
+ std::cout << " prefix:" << prefix << std::endl;
+ }
+ virtual void startElement(const std::string& namespaceURI, const std::string& localName,
+ const std::string& qName, const AttributesT& atts) {
+ std::cout << "startElement" << std::endl;
+ std::cout << " namespaceURI:" << namespaceURI << std::endl;
+ std::cout << " localName:" << localName << std::endl;
+ std::cout << " qName:" << qName << std::endl;
+ std::cout << " atts:" << atts.getLength() << std::endl;
+ }
+ virtual void endElement(const std::string& namespaceURI, const std::string& localName,
+ const std::string& qName) {
+ std::cout << "endElement" << std::endl;
+ std::cout << " namespaceURI:" << namespaceURI << std::endl;
+ std::cout << " localName:" << localName << std::endl;
+ std::cout << " qName:" << qName << std::endl;
+ }
+ virtual void characters(const std::string& ch) {
+ std::cout << "characters" << std::endl;
+ std::cout << " ch:" << ch << std::endl;
+ }
+ virtual void ignorableWhitespace(const std::string& ch) {
+ std::cout << "ignorableWhitespace" << std::endl;
+ std::cout << " ch:" << ch << std::endl;
+ }
+ virtual void processingInstruction(const std::string& target, const std::string& data) {
+ std::cout << "processingInstruction" << std::endl;
+ std::cout << " target:" << target << std::endl;
+ std::cout << " data:" << data << std::endl;
+ }
+ virtual void skippedEntity(const std::string& name) {
+ std::cout << "skippedEntity" << std::endl;
+ std::cout << " name:" << name << std::endl;
+ }
+
+ // ErrorHandler
+ virtual void warning(const SAXParseExceptionT& e) { Arabica::SAX::CatchErrorHandler<std::string>::warning(e); }
+ virtual void error(const SAXParseExceptionT& e) { Arabica::SAX::CatchErrorHandler<std::string>::error(e); }
+ virtual void fatalError(const SAXParseExceptionT& e) {
+ Arabica::SAX::CatchErrorHandler<std::string>::fatalError(e);
+ }
+
+ // LexicalHandler
+ virtual void startDTD(const std::string& name,
+ const std::string& publicId,
+ const std::string& systemId) {
+ std::cout << "startDTD" << std::endl;
+ std::cout << " name:" << name << std::endl;
+ std::cout << " publicId:" << publicId << std::endl;
+ std::cout << " systemId:" << systemId << std::endl;
+ }
+
+ virtual void endDTD() {
+ std::cout << "endDTD" << std::endl;
+ }
+ virtual void startEntity(const std::string& name) {
+ std::cout << "startEntity" << std::endl;
+ std::cout << " name:" << name << std::endl;
+ }
+ virtual void endEntity(const std::string& name) {
+ std::cout << "endEntity" << std::endl;
+ std::cout << " name:" << name << std::endl;
+ }
+ virtual void startCDATA() {
+ std::cout << "startCDATA" << std::endl;
+ }
+ virtual void endCDATA() {
+ std::cout << "endCDATA" << std::endl;
+ }
+ virtual void comment(const std::string& text) {
+ std::cout << "comment" << std::endl;
+ std::cout << " text:" << text << std::endl;
+ }
+
+ // DeclHandler
+ virtual void elementDecl(const std::string& name, const std::string& model) {
+ std::cout << "elementDecl" << std::endl;
+ std::cout << " name:" << name << std::endl;
+ std::cout << " model:" << model << std::endl;
+ }
+ virtual void attributeDecl(const std::string& elementName,
+ const std::string& attributeName,
+ const std::string& type,
+ const std::string& valueDefault,
+ const std::string& value) {
+ std::cout << "attributeDecl" << std::endl;
+ std::cout << " elementName:" << elementName << std::endl;
+ std::cout << " attributeName:" << attributeName << std::endl;
+ std::cout << " type:" << type << std::endl;
+ std::cout << " valueDefault:" << valueDefault << std::endl;
+ std::cout << " value:" << value << std::endl;
+ }
+ virtual void internalEntityDecl(const std::string& name, const std::string& value) {
+ std::cout << "internalEntityDecl" << std::endl;
+ std::cout << " name:" << name << std::endl;
+ std::cout << " value:" << value << std::endl;
+ }
+ virtual void externalEntityDecl(const std::string& name,
+ const std::string& publicId,
+ const std::string& systemId) {
+ std::cout << "externalEntityDecl" << std::endl;
+ std::cout << " name:" << name << std::endl;
+ std::cout << " publicId:" << publicId << std::endl;
+ std::cout << " systemId:" << systemId << std::endl;
+ }
+
+ };
+#endif
+}
+
+#endif
diff --git a/src/uscxml/Message.cpp b/src/uscxml/Message.cpp
new file mode 100644
index 0000000..3d3048b
--- /dev/null
+++ b/src/uscxml/Message.cpp
@@ -0,0 +1,168 @@
+#include "uscxml/Message.h"
+#include <DOM/SAX2DOM/SAX2DOM.hpp>
+#include <SAX/helpers/CatchErrorHandler.hpp>
+
+namespace uscxml {
+
+static int _dataIndentation = 1;
+
+
+Arabica::DOM::Document<std::string> Data::toDocument() {
+ Arabica::DOM::DOMImplementation<std::string> domFactory = Arabica::SimpleDOM::DOMImplementation<std::string>::getDOMImplementation();
+ Arabica::DOM::Document<std::string> document = domFactory.createDocument("http://www.w3.org/2005/07/scxml", "message", 0);
+ Arabica::DOM::Element<std::string> scxmlMsg = document.getDocumentElement();
+ scxmlMsg.setPrefix("scxml");
+ scxmlMsg.setAttribute("version", "1.0");
+
+ if (compound.size() > 0 || array.size() > 0) {
+ Arabica::DOM::Element<std::string> payloadElem = document.createElementNS("http://www.w3.org/2005/07/scxml", "scxml:payload");
+ scxmlMsg.appendChild(payloadElem);
+
+ // we do not support nested attibutes
+ if (compound.size() > 0) {
+ std::map<std::string, Data>::iterator compoundIter = compound.begin();
+ while(compoundIter != compound.end()) {
+ if (compoundIter->second.atom.size() > 0) {
+ Arabica::DOM::Element<std::string> propertyElem = document.createElementNS("http://www.w3.org/2005/07/scxml", "scxml:property");
+ propertyElem.setAttribute("name", compoundIter->first);
+ Arabica::DOM::Text<std::string> textElem = document.createTextNode(compoundIter->second.atom);
+ propertyElem.appendChild(textElem);
+ payloadElem.appendChild(propertyElem);
+ }
+ compoundIter++;
+ }
+ }
+ }
+ return document;
+}
+
+Arabica::DOM::Document<std::string> Event::toDocument() {
+ Arabica::DOM::DOMImplementation<std::string> domFactory = Arabica::SimpleDOM::DOMImplementation<std::string>::getDOMImplementation();
+ Arabica::DOM::Document<std::string> document = Data::toDocument();
+ Arabica::DOM::Element<std::string> scxmlMsg = document.getDocumentElement();
+
+ scxmlMsg.setAttribute("source", origin);
+ scxmlMsg.setAttribute("name", name);
+
+ return document;
+}
+
+Arabica::DOM::Document<std::string> SendRequest::toDocument() {
+ Arabica::DOM::DOMImplementation<std::string> domFactory = Arabica::SimpleDOM::DOMImplementation<std::string>::getDOMImplementation();
+ Arabica::DOM::Document<std::string> document = Event::toDocument();
+ Arabica::DOM::Element<std::string> scxmlMsg = document.getDocumentElement();
+
+ scxmlMsg.setAttribute("sendid", sendid);
+
+ return document;
+}
+
+Arabica::DOM::Document<std::string> InvokeRequest::toDocument() {
+ Arabica::DOM::DOMImplementation<std::string> domFactory = Arabica::SimpleDOM::DOMImplementation<std::string>::getDOMImplementation();
+ Arabica::DOM::Document<std::string> document = Event::toDocument();
+ Arabica::DOM::Element<std::string> scxmlMsg = document.getDocumentElement();
+
+ scxmlMsg.setAttribute("invokeid", invokeid);
+
+ return document;
+}
+
+Data Data::fromXML(const std::string& xmlString) {
+}
+
+Event Event::fromXML(const std::string& xmlString) {
+ Arabica::SAX2DOM::Parser<std::string> eventParser;
+ Arabica::SAX::CatchErrorHandler<std::string> errorHandler;
+ eventParser.setErrorHandler(errorHandler);
+
+ std::istringstream is(xmlString);
+ Arabica::SAX::InputSource<std::string> inputSource;
+ inputSource.setByteStream(is);
+
+ Event event;
+ if(eventParser.parse(inputSource) && eventParser.getDocument().hasChildNodes()) {
+ Arabica::DOM::Element<std::string> scxmlMsg = eventParser.getDocument().getDocumentElement();
+ if (HAS_ATTR(scxmlMsg, "name"))
+ event.name = ATTR(scxmlMsg, "name");
+ if (HAS_ATTR(scxmlMsg, "sendid"))
+ event.sendid = ATTR(scxmlMsg, "sendid");
+ }
+ return event;
+}
+
+SendRequest SendRequest::fromXML(const std::string& xmlString) {
+ Event::fromXML(xmlString);
+}
+
+InvokeRequest InvokeRequest::fromXML(const std::string& xmlString) {
+ Event::fromXML(xmlString);
+}
+
+#ifndef SWIGJAVA
+std::ostream& operator<< (std::ostream& os, const Event& event) {
+ os << (event.type == Event::EXTERNAL ? "External" : "Internal") << " Event " << (event.dom ? "with DOM attached" : "") << std::endl;
+
+ if (event.name.size() > 0)
+ os << " name: " << event.name << std::endl;
+ if (event.origin.size() > 0)
+ os << " origin: " << event.origin << std::endl;
+ if (event.origintype.size() > 0)
+ os << " origintype: " << event.origintype << std::endl;
+ _dataIndentation++;
+ os << " data: " << (Data)event << std::endl;
+ _dataIndentation--;
+ return os;
+}
+#endif
+
+#ifndef SWIGJAVA
+std::ostream& operator<< (std::ostream& os, const Data& data) {
+ std::string indent;
+ for (int i = 0; i < _dataIndentation; i++) {
+ indent += " ";
+ }
+ if (false) {
+ } else if (data.compound.size() > 0) {
+ int longestKey = 0;
+ std::map<std::string, Data>::const_iterator compoundIter = data.compound.begin();
+ while(compoundIter != data.compound.end()) {
+ if (compoundIter->first.size() > longestKey)
+ longestKey = compoundIter->first.size();
+ compoundIter++;
+ }
+ std::string keyPadding;
+ for (unsigned int i = 0; i < longestKey; i++)
+ keyPadding += " ";
+
+ os << "{" << std::endl;
+ compoundIter = data.compound.begin();
+ while(compoundIter != data.compound.end()) {
+ os << indent << " \"" << compoundIter->first << "\" " << keyPadding.substr(0, longestKey - compoundIter->first.size()) << "= ";
+ _dataIndentation += 2;
+ os << compoundIter->second << "," << std::endl;
+ _dataIndentation -= 2;
+ compoundIter++;
+ }
+ os << indent << "}" << std::endl;
+ } else if (data.array.size() > 0) {
+ os << "[" << std::endl;
+ std::map<std::string, Data>::const_iterator compoundIter = data.compound.begin();
+ while(compoundIter != data.compound.end()) {
+ _dataIndentation += 2;
+ os << indent << " " << compoundIter->second << "," << std::endl;
+ _dataIndentation -= 2;
+ compoundIter++;
+ }
+ os << indent << "]" << std::endl;
+ } else if (data.atom.size() > 0) {
+ if (data.type == Data::VERBATIM) {
+ os << indent << "\"" << data.atom << "\"";
+ } else {
+ os << indent << data.atom;
+ }
+ }
+ return os;
+}
+#endif
+
+} \ No newline at end of file
diff --git a/src/uscxml/Message.h b/src/uscxml/Message.h
new file mode 100644
index 0000000..a520ff5
--- /dev/null
+++ b/src/uscxml/Message.h
@@ -0,0 +1,122 @@
+#ifndef EVENT_H_XZAQ4HR
+#define EVENT_H_XZAQ4HR
+
+#include <map>
+#include <vector>
+#include <string>
+
+#include <DOM/Document.hpp>
+#include <DOM/io/Stream.hpp>
+
+#define TAGNAME(elem) ((Arabica::DOM::Element<std::string>)elem).getTagName()
+#define ATTR(elem, attr) ((Arabica::DOM::Element<std::string>)elem).getAttribute(attr)
+#define HAS_ATTR(elem, attr) ((Arabica::DOM::Element<std::string>)elem).hasAttribute(attr)
+
+namespace uscxml {
+
+class Data {
+public:
+ enum Type {
+ VERBATIM,
+ INTERPRETED
+ };
+
+ Data() {}
+ Data(const std::string& atom_, Type type_ = INTERPRETED) : atom(atom_), type(type_) {}
+ virtual ~Data() {}
+
+ static Data fromXML(const std::string& xmlString);
+ Arabica::DOM::Document<std::string> toDocument();
+ std::string toXMLString() {
+ std::stringstream ss;
+ ss << toDocument();
+ return ss.str();
+ }
+
+ std::map<std::string, Data> compound;
+ std::vector<Data> array;
+ std::string atom;
+ Type type;
+
+protected:
+ Arabica::DOM::Document<std::string> toNode(const Arabica::DOM::Document<std::string>& factory, const Data& data);
+
+#ifndef SWIGJAVA
+ friend std::ostream& operator<< (std::ostream& os, const Data& data);
+#endif
+};
+
+class Event : public Data {
+public:
+ enum Type {
+ PLATFORM,
+ INTERNAL,
+ EXTERNAL
+ };
+
+ Event() : type(INTERNAL) {}
+
+ std::string name;
+ Type type;
+ std::string origin;
+ std::string origintype;
+ Arabica::DOM::Node<std::string> dom;
+ std::string sendid;
+ std::string invokeid;
+
+ static Event fromXML(const std::string& xmlString);
+ Arabica::DOM::Document<std::string> toDocument();
+ std::string toXMLString() {
+ std::stringstream ss;
+ ss << toDocument();
+ return ss.str();
+ }
+
+#ifndef SWIGJAVA
+ friend std::ostream& operator<< (std::ostream& os, const Event& event);
+#endif
+};
+
+class InvokeRequest : public Event {
+public:
+ std::string type;
+ std::string src;
+ std::string namelist;
+ bool autoForward;
+ Arabica::DOM::Node<std::string> finalize;
+ std::map<std::string, std::string> params;
+ std::string content;
+
+ static InvokeRequest fromXML(const std::string& xmlString);
+ Arabica::DOM::Document<std::string> toDocument();
+ std::string toXMLString() {
+ std::stringstream ss;
+ ss << toDocument();
+ return ss.str();
+ }
+
+};
+
+class SendRequest : public Event {
+public:
+ std::string target;
+ std::string type;
+ uint32_t delayMs;
+ std::map<std::string, std::string> params;
+ std::map<std::string, std::string> namelist;
+ std::string content;
+
+ static SendRequest fromXML(const std::string& xmlString);
+ Arabica::DOM::Document<std::string> toDocument();
+ std::string toXMLString() {
+ std::stringstream ss;
+ ss << toDocument();
+ return ss.str();
+ }
+
+};
+
+}
+
+
+#endif /* end of include guard: EVENT_H_XZAQ4HR */
diff --git a/src/uscxml/concurrency/BlockingQueue.h b/src/uscxml/concurrency/BlockingQueue.h
new file mode 100644
index 0000000..90094bf
--- /dev/null
+++ b/src/uscxml/concurrency/BlockingQueue.h
@@ -0,0 +1,41 @@
+#ifndef BLOCKINGQUEUE_H_4LEVMY0N
+#define BLOCKINGQUEUE_H_4LEVMY0N
+
+#include "uscxml/concurrency/tinythread.h"
+#include <list>
+
+namespace uscxml {
+namespace concurrency {
+
+template <class T>
+class BlockingQueue {
+public:
+ BlockingQueue() {}
+ virtual ~BlockingQueue() {
+ }
+
+ void push(T elem) {
+ tthread::lock_guard<tthread::mutex> lock(_mutex);
+ _queue.push_back(elem);
+ _cond.notify_all();
+ }
+
+ T pop() {
+ tthread::lock_guard<tthread::mutex> lock(_mutex);
+ while (_queue.empty()) {
+ _cond.wait(_mutex);
+ }
+ T ret = _queue.front();
+ _queue.pop_front();
+ return ret;
+ }
+
+ tthread::mutex _mutex;
+ tthread::condition_variable _cond;
+ std::list<T> _queue;
+};
+
+}
+}
+
+#endif /* end of include guard: BLOCKINGQUEUE_H_4LEVMY0N */
diff --git a/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.cpp b/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.cpp
new file mode 100644
index 0000000..a93b14a
--- /dev/null
+++ b/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.cpp
@@ -0,0 +1,57 @@
+#include "uscxml/concurrency/DelayedEventQueue.h"
+#include <assert.h>
+
+namespace uscxml {
+
+ DelayedEventQueue::DelayedEventQueue() {
+ _eventLoop = EV_DEFAULT;
+ _thread = NULL;
+ }
+
+ DelayedEventQueue::~DelayedEventQueue() {
+ ev_break(_eventLoop);
+ if (_thread)
+ _thread->join();
+ }
+
+ void DelayedEventQueue::addEvent(std::string eventId, void (*callback)(void*, const std::string eventId), uint32_t delayMs, void* userData) {
+ if(_timeoutWatcher.find(eventId) != _timeoutWatcher.end()) {
+ cancelEvent(eventId);
+ }
+
+ _timeoutWatcher[eventId].eventId = eventId;
+ _timeoutWatcher[eventId].userData = userData;
+ _timeoutWatcher[eventId].eventQueue = this;
+ _timeoutWatcher[eventId].callback = callback;
+
+ ev_timer_init (&_timeoutWatcher[eventId].io, DelayedEventQueue::timerCallback, ((float)delayMs)/1000.0f, 0.);
+ ev_timer_start (_eventLoop, &_timeoutWatcher[eventId].io);
+
+ }
+
+ void DelayedEventQueue::cancelEvent(std::string eventId) {
+ if(_timeoutWatcher.find(eventId) != _timeoutWatcher.end()) {
+ ev_timer_stop(_eventLoop, &_timeoutWatcher[eventId].io);
+ _timeoutWatcher.erase(eventId);
+ }
+ }
+
+ void DelayedEventQueue::start() {
+ _thread = new tthread::thread(DelayedEventQueue::run, this);
+ }
+
+ void DelayedEventQueue::stop() {
+ }
+
+ void DelayedEventQueue::run(void* instance) {
+ ev_run (((DelayedEventQueue*)instance)->_eventLoop, 0);
+ }
+
+ void DelayedEventQueue::timerCallback(EV_P_ ev_timer *w, int revents) {
+ struct callbackData *data = (struct callbackData*)w;
+ std::string eventId = data->eventId; // copy eventId
+ data->eventQueue->_timeoutWatcher.erase(data->eventId);
+ data->callback(data->userData, eventId);
+ }
+
+} \ No newline at end of file
diff --git a/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.h b/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.h
new file mode 100644
index 0000000..2bc71b2
--- /dev/null
+++ b/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.h
@@ -0,0 +1,45 @@
+#ifndef DELAYEDEVENTQUEUE_H_JA6WRBVP
+#define DELAYEDEVENTQUEUE_H_JA6WRBVP
+
+#include "tinythread.h"
+#include <ev.h>
+
+#include <map>
+#include <string>
+#include <iostream>
+
+namespace uscxml {
+
+class DelayedEventQueue {
+public:
+
+ struct callbackData
+ {
+ ev_timer io;
+ void *userData;
+ void (*callback)(void*, const std::string eventId);
+ std::string eventId;
+ DelayedEventQueue* eventQueue;
+ };
+
+ DelayedEventQueue();
+ virtual ~DelayedEventQueue();
+
+ void addEvent(std::string eventId, void (*callback)(void*, const std::string eventId), uint32_t delayMs, void* userData);
+ void cancelEvent(std::string eventId);
+
+ void start();
+ void stop();
+ static void run(void*);
+
+ static void timerCallback(EV_P_ ev_timer *w, int revents);
+
+ tthread::thread* _thread;
+ std::map<std::string, callbackData> _timeoutWatcher;
+ struct ev_loop* _eventLoop;
+};
+
+}
+
+
+#endif /* end of include guard: DELAYEDEVENTQUEUE_H_JA6WRBVP */
diff --git a/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.cpp b/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.cpp
new file mode 100644
index 0000000..ce42af7
--- /dev/null
+++ b/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.cpp
@@ -0,0 +1,87 @@
+#include "uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h"
+#include <assert.h>
+#include <event2/event.h>
+
+namespace uscxml {
+
+ DelayedEventQueue::DelayedEventQueue() {
+ evthread_use_pthreads();
+ _eventLoop = event_base_new();
+ _thread = NULL;
+ }
+
+ DelayedEventQueue::~DelayedEventQueue() {
+ std::cout << "Deleting DelayedEventQueue" << std::endl;
+ if(_eventLoop)
+ event_base_loopbreak(_eventLoop);
+ if (_thread)
+ _thread->join();
+ if(_eventLoop)
+ event_base_free(_eventLoop);
+ }
+
+ void DelayedEventQueue::run(void* instance) {
+ DelayedEventQueue* THIS = (DelayedEventQueue*)instance;
+ int result;
+ while(THIS->_isStarted) {
+ {
+ //result = event_base_dispatch(THIS->_eventLoop);
+ result = event_base_loop(THIS->_eventLoop, EVLOOP_NO_EXIT_ON_EMPTY);
+ }
+ }
+ }
+
+ void DelayedEventQueue::addEvent(std::string eventId, void (*callback)(void*, const std::string eventId), uint32_t delayMs, void* userData) {
+ if(_callbackData.find(eventId) != _callbackData.end()) {
+ cancelEvent(eventId);
+ }
+
+ struct timeval delay = {delayMs / 1000, (delayMs % 1000) * 1000};
+ struct event* event = event_new(_eventLoop, -1, 0, DelayedEventQueue::timerCallback, &_callbackData[eventId]);
+
+ _callbackData[eventId].eventId = eventId;
+ _callbackData[eventId].userData = userData;
+ _callbackData[eventId].eventQueue = this;
+ _callbackData[eventId].callback = callback;
+ _callbackData[eventId].event = event;
+
+ event_add(event, &delay);
+ }
+
+ void DelayedEventQueue::cancelEvent(std::string eventId) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
+
+ if(_callbackData.find(eventId) != _callbackData.end()) {
+ event_del(_callbackData[eventId].event);
+ event_free(_callbackData[eventId].event);
+ _callbackData.erase(eventId);
+ }
+ }
+
+ void DelayedEventQueue::start() {
+ _isStarted = true;
+ _thread = new tthread::thread(DelayedEventQueue::run, this);
+ }
+
+ void DelayedEventQueue::stop() {
+ if (_isStarted) {
+ _isStarted = false;
+ _thread->join();
+ delete _thread;
+ }
+ }
+
+ void DelayedEventQueue::dummyCallback(evutil_socket_t fd, short what, void *arg) {
+ }
+
+ void DelayedEventQueue::timerCallback(evutil_socket_t fd, short what, void *arg) {
+ struct callbackData *data = (struct callbackData*)arg;
+ tthread::lock_guard<tthread::recursive_mutex> lock(data->eventQueue->_mutex);
+
+ std::string eventId = data->eventId; // copy eventId
+ event_free(data->event);
+ data->eventQueue->_callbackData.erase(data->eventId);
+ data->callback(data->userData, eventId);
+ }
+
+} \ No newline at end of file
diff --git a/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h b/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h
new file mode 100644
index 0000000..4c59ce1
--- /dev/null
+++ b/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h
@@ -0,0 +1,52 @@
+#ifndef DELAYEDEVENTQUEUE_H_JA6WRBVP
+#define DELAYEDEVENTQUEUE_H_JA6WRBVP
+
+#include "uscxml/concurrency/tinythread.h"
+
+#include <event2/thread.h>
+#include <event2/http.h>
+#include <event2/event.h>
+
+#include <map>
+#include <string>
+#include <iostream>
+
+namespace uscxml {
+
+class DelayedEventQueue {
+public:
+
+ struct callbackData
+ {
+ void *userData;
+ void (*callback)(void*, const std::string eventId);
+ std::string eventId;
+ struct event *event;
+ DelayedEventQueue* eventQueue;
+ };
+
+ DelayedEventQueue();
+ virtual ~DelayedEventQueue();
+
+ void addEvent(std::string eventId, void (*callback)(void*, const std::string eventId), uint32_t delayMs, void* userData);
+ void cancelEvent(std::string eventId);
+
+ void start();
+ void stop();
+ static void run(void*);
+
+ static void timerCallback(evutil_socket_t fd, short what, void *arg);
+ static void dummyCallback(evutil_socket_t fd, short what, void *arg);
+
+ bool _isStarted;
+ tthread::thread* _thread;
+ tthread::recursive_mutex _mutex;
+
+ std::map<std::string, callbackData> _callbackData;
+ struct event_base* _eventLoop;
+};
+
+}
+
+
+#endif /* end of include guard: DELAYEDEVENTQUEUE_H_JA6WRBVP */
diff --git a/src/uscxml/concurrency/tinythread.cpp b/src/uscxml/concurrency/tinythread.cpp
new file mode 100644
index 0000000..690ecee
--- /dev/null
+++ b/src/uscxml/concurrency/tinythread.cpp
@@ -0,0 +1,303 @@
+/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*-
+Copyright (c) 2010-2012 Marcus Geelnard
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source
+ distribution.
+*/
+
+#include <exception>
+#include "tinythread.h"
+
+#if defined(_TTHREAD_POSIX_)
+ #include <unistd.h>
+ #include <map>
+#elif defined(_TTHREAD_WIN32_)
+ #include <process.h>
+#endif
+
+
+namespace tthread {
+
+//------------------------------------------------------------------------------
+// condition_variable
+//------------------------------------------------------------------------------
+// NOTE 1: The Win32 implementation of the condition_variable class is based on
+// the corresponding implementation in GLFW, which in turn is based on a
+// description by Douglas C. Schmidt and Irfan Pyarali:
+// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
+//
+// NOTE 2: Windows Vista actually has native support for condition variables
+// (InitializeConditionVariable, WakeConditionVariable, etc), but we want to
+// be portable with pre-Vista Windows versions, so TinyThread++ does not use
+// Vista condition variables.
+//------------------------------------------------------------------------------
+
+#if defined(_TTHREAD_WIN32_)
+ #define _CONDITION_EVENT_ONE 0
+ #define _CONDITION_EVENT_ALL 1
+#endif
+
+#if defined(_TTHREAD_WIN32_)
+condition_variable::condition_variable() : mWaitersCount(0)
+{
+ mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL);
+ mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL);
+ InitializeCriticalSection(&mWaitersCountLock);
+}
+#endif
+
+#if defined(_TTHREAD_WIN32_)
+condition_variable::~condition_variable()
+{
+ CloseHandle(mEvents[_CONDITION_EVENT_ONE]);
+ CloseHandle(mEvents[_CONDITION_EVENT_ALL]);
+ DeleteCriticalSection(&mWaitersCountLock);
+}
+#endif
+
+#if defined(_TTHREAD_WIN32_)
+void condition_variable::_wait()
+{
+ // Wait for either event to become signaled due to notify_one() or
+ // notify_all() being called
+ int result = WaitForMultipleObjects(2, mEvents, FALSE, INFINITE);
+
+ // Check if we are the last waiter
+ EnterCriticalSection(&mWaitersCountLock);
+ -- mWaitersCount;
+ bool lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) &&
+ (mWaitersCount == 0);
+ LeaveCriticalSection(&mWaitersCountLock);
+
+ // If we are the last waiter to be notified to stop waiting, reset the event
+ if(lastWaiter)
+ ResetEvent(mEvents[_CONDITION_EVENT_ALL]);
+}
+#endif
+
+#if defined(_TTHREAD_WIN32_)
+void condition_variable::notify_one()
+{
+ // Are there any waiters?
+ EnterCriticalSection(&mWaitersCountLock);
+ bool haveWaiters = (mWaitersCount > 0);
+ LeaveCriticalSection(&mWaitersCountLock);
+
+ // If we have any waiting threads, send them a signal
+ if(haveWaiters)
+ SetEvent(mEvents[_CONDITION_EVENT_ONE]);
+}
+#endif
+
+#if defined(_TTHREAD_WIN32_)
+void condition_variable::notify_all()
+{
+ // Are there any waiters?
+ EnterCriticalSection(&mWaitersCountLock);
+ bool haveWaiters = (mWaitersCount > 0);
+ LeaveCriticalSection(&mWaitersCountLock);
+
+ // If we have any waiting threads, send them a signal
+ if(haveWaiters)
+ SetEvent(mEvents[_CONDITION_EVENT_ALL]);
+}
+#endif
+
+
+//------------------------------------------------------------------------------
+// POSIX pthread_t to unique thread::id mapping logic.
+// Note: Here we use a global thread safe std::map to convert instances of
+// pthread_t to small thread identifier numbers (unique within one process).
+// This method should be portable across different POSIX implementations.
+//------------------------------------------------------------------------------
+
+#if defined(_TTHREAD_POSIX_)
+static thread::id _pthread_t_to_ID(const pthread_t &aHandle)
+{
+ static mutex idMapLock;
+ static std::map<pthread_t, unsigned long int> idMap;
+ static unsigned long int idCount(1);
+
+ lock_guard<mutex> guard(idMapLock);
+ if(idMap.find(aHandle) == idMap.end())
+ idMap[aHandle] = idCount ++;
+ return thread::id(idMap[aHandle]);
+}
+#endif // _TTHREAD_POSIX_
+
+
+//------------------------------------------------------------------------------
+// thread
+//------------------------------------------------------------------------------
+
+/// Information to pass to the new thread (what to run).
+struct _thread_start_info {
+ void (*mFunction)(void *); ///< Pointer to the function to be executed.
+ void * mArg; ///< Function argument for the thread function.
+ thread * mThread; ///< Pointer to the thread object.
+};
+
+// Thread wrapper function.
+#if defined(_TTHREAD_WIN32_)
+unsigned WINAPI thread::wrapper_function(void * aArg)
+#elif defined(_TTHREAD_POSIX_)
+void * thread::wrapper_function(void * aArg)
+#endif
+{
+ // Get thread startup information
+ _thread_start_info * ti = (_thread_start_info *) aArg;
+
+ try
+ {
+ // Call the actual client thread function
+ ti->mFunction(ti->mArg);
+ }
+ catch(...)
+ {
+ // Uncaught exceptions will terminate the application (default behavior
+ // according to C++11)
+ std::terminate();
+ }
+
+ // The thread is no longer executing
+ lock_guard<mutex> guard(ti->mThread->mDataMutex);
+ ti->mThread->mNotAThread = true;
+
+ // The thread is responsible for freeing the startup information
+ delete ti;
+
+ return 0;
+}
+
+thread::thread(void (*aFunction)(void *), void * aArg)
+{
+ // Serialize access to this thread structure
+ lock_guard<mutex> guard(mDataMutex);
+
+ // Fill out the thread startup information (passed to the thread wrapper,
+ // which will eventually free it)
+ _thread_start_info * ti = new _thread_start_info;
+ ti->mFunction = aFunction;
+ ti->mArg = aArg;
+ ti->mThread = this;
+
+ // The thread is now alive
+ mNotAThread = false;
+
+ // Create the thread
+#if defined(_TTHREAD_WIN32_)
+ mHandle = (HANDLE) _beginthreadex(0, 0, wrapper_function, (void *) ti, 0, &mWin32ThreadID);
+#elif defined(_TTHREAD_POSIX_)
+ if(pthread_create(&mHandle, NULL, wrapper_function, (void *) ti) != 0)
+ mHandle = 0;
+#endif
+
+ // Did we fail to create the thread?
+ if(!mHandle)
+ {
+ mNotAThread = true;
+ delete ti;
+ }
+}
+
+thread::~thread()
+{
+ if(joinable())
+ std::terminate();
+}
+
+void thread::join()
+{
+ if(joinable())
+ {
+#if defined(_TTHREAD_WIN32_)
+ WaitForSingleObject(mHandle, INFINITE);
+ CloseHandle(mHandle);
+#elif defined(_TTHREAD_POSIX_)
+ pthread_join(mHandle, NULL);
+#endif
+ }
+}
+
+bool thread::joinable() const
+{
+ mDataMutex.lock();
+ bool result = !mNotAThread;
+ mDataMutex.unlock();
+ return result;
+}
+
+void thread::detach()
+{
+ mDataMutex.lock();
+ if(!mNotAThread)
+ {
+#if defined(_TTHREAD_WIN32_)
+ CloseHandle(mHandle);
+#elif defined(_TTHREAD_POSIX_)
+ pthread_detach(mHandle);
+#endif
+ mNotAThread = true;
+ }
+ mDataMutex.unlock();
+}
+
+thread::id thread::get_id() const
+{
+ if(!joinable())
+ return id();
+#if defined(_TTHREAD_WIN32_)
+ return id((unsigned long int) mWin32ThreadID);
+#elif defined(_TTHREAD_POSIX_)
+ return _pthread_t_to_ID(mHandle);
+#endif
+}
+
+unsigned thread::hardware_concurrency()
+{
+#if defined(_TTHREAD_WIN32_)
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ return (int) si.dwNumberOfProcessors;
+#elif defined(_SC_NPROCESSORS_ONLN)
+ return (int) sysconf(_SC_NPROCESSORS_ONLN);
+#elif defined(_SC_NPROC_ONLN)
+ return (int) sysconf(_SC_NPROC_ONLN);
+#else
+ // The standard requires this function to return zero if the number of
+ // hardware cores could not be determined.
+ return 0;
+#endif
+}
+
+
+//------------------------------------------------------------------------------
+// this_thread
+//------------------------------------------------------------------------------
+
+thread::id this_thread::get_id()
+{
+#if defined(_TTHREAD_WIN32_)
+ return thread::id((unsigned long int) GetCurrentThreadId());
+#elif defined(_TTHREAD_POSIX_)
+ return _pthread_t_to_ID(pthread_self());
+#endif
+}
+
+}
diff --git a/src/uscxml/concurrency/tinythread.h b/src/uscxml/concurrency/tinythread.h
new file mode 100644
index 0000000..aed7b58
--- /dev/null
+++ b/src/uscxml/concurrency/tinythread.h
@@ -0,0 +1,714 @@
+/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*-
+Copyright (c) 2010-2012 Marcus Geelnard
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source
+ distribution.
+*/
+
+#ifndef _TINYTHREAD_H_
+#define _TINYTHREAD_H_
+
+/// @file
+/// @mainpage TinyThread++ API Reference
+///
+/// @section intro_sec Introduction
+/// TinyThread++ is a minimal, portable implementation of basic threading
+/// classes for C++.
+///
+/// They closely mimic the functionality and naming of the C++11 standard, and
+/// should be easily replaceable with the corresponding std:: variants.
+///
+/// @section port_sec Portability
+/// The Win32 variant uses the native Win32 API for implementing the thread
+/// classes, while for other systems, the POSIX threads API (pthread) is used.
+///
+/// @section class_sec Classes
+/// In order to mimic the threading API of the C++11 standard, subsets of
+/// several classes are provided. The fundamental classes are:
+/// @li tthread::thread
+/// @li tthread::mutex
+/// @li tthread::recursive_mutex
+/// @li tthread::condition_variable
+/// @li tthread::lock_guard
+/// @li tthread::fast_mutex
+///
+/// @section misc_sec Miscellaneous
+/// The following special keywords are available: #thread_local.
+///
+/// For more detailed information (including additional classes), browse the
+/// different sections of this documentation. A good place to start is:
+/// tinythread.h.
+
+// Which platform are we on?
+#if !defined(_TTHREAD_PLATFORM_DEFINED_)
+ #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__)
+ #define _TTHREAD_WIN32_
+ #else
+ #define _TTHREAD_POSIX_
+ #endif
+ #define _TTHREAD_PLATFORM_DEFINED_
+#endif
+
+// Platform specific includes
+#if defined(_TTHREAD_WIN32_)
+ #ifndef WIN32_LEAN_AND_MEAN
+ #define WIN32_LEAN_AND_MEAN
+ #define __UNDEF_LEAN_AND_MEAN
+ #endif
+ #include <windows.h>
+ #ifdef __UNDEF_LEAN_AND_MEAN
+ #undef WIN32_LEAN_AND_MEAN
+ #undef __UNDEF_LEAN_AND_MEAN
+ #endif
+#else
+ #include <pthread.h>
+ #include <signal.h>
+ #include <sched.h>
+ #include <unistd.h>
+#endif
+
+// Generic includes
+#include <ostream>
+
+/// TinyThread++ version (major number).
+#define TINYTHREAD_VERSION_MAJOR 1
+/// TinyThread++ version (minor number).
+#define TINYTHREAD_VERSION_MINOR 1
+/// TinyThread++ version (full version).
+#define TINYTHREAD_VERSION (TINYTHREAD_VERSION_MAJOR * 100 + TINYTHREAD_VERSION_MINOR)
+
+// Do we have a fully featured C++11 compiler?
+#if (__cplusplus > 199711L) || (defined(__STDCXX_VERSION__) && (__STDCXX_VERSION__ >= 201001L))
+ #define _TTHREAD_CPP11_
+#endif
+
+// ...at least partial C++11?
+#if defined(_TTHREAD_CPP11_) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__)
+ #define _TTHREAD_CPP11_PARTIAL_
+#endif
+
+// Macro for disabling assignments of objects.
+#ifdef _TTHREAD_CPP11_PARTIAL_
+ #define _TTHREAD_DISABLE_ASSIGNMENT(name) \
+ name(const name&) = delete; \
+ name& operator=(const name&) = delete;
+#else
+ #define _TTHREAD_DISABLE_ASSIGNMENT(name) \
+ name(const name&); \
+ name& operator=(const name&);
+#endif
+
+/// @def thread_local
+/// Thread local storage keyword.
+/// A variable that is declared with the @c thread_local keyword makes the
+/// value of the variable local to each thread (known as thread-local storage,
+/// or TLS). Example usage:
+/// @code
+/// // This variable is local to each thread.
+/// thread_local int variable;
+/// @endcode
+/// @note The @c thread_local keyword is a macro that maps to the corresponding
+/// compiler directive (e.g. @c __declspec(thread)). While the C++11 standard
+/// allows for non-trivial types (e.g. classes with constructors and
+/// destructors) to be declared with the @c thread_local keyword, most pre-C++11
+/// compilers only allow for trivial types (e.g. @c int). So, to guarantee
+/// portable code, only use trivial types for thread local storage.
+/// @note This directive is currently not supported on Mac OS X (it will give
+/// a compiler error), since compile-time TLS is not supported in the Mac OS X
+/// executable format. Also, some older versions of MinGW (before GCC 4.x) do
+/// not support this directive.
+/// @hideinitializer
+
+#if !defined(_TTHREAD_CPP11_) && !defined(thread_local)
+ #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__)
+ #define thread_local __thread
+ #else
+ #define thread_local __declspec(thread)
+ #endif
+#endif
+
+
+/// Main name space for TinyThread++.
+/// This namespace is more or less equivalent to the @c std namespace for the
+/// C++11 thread classes. For instance, the tthread::mutex class corresponds to
+/// the std::mutex class.
+namespace tthread {
+
+/// Mutex class.
+/// This is a mutual exclusion object for synchronizing access to shared
+/// memory areas for several threads. The mutex is non-recursive (i.e. a
+/// program may deadlock if the thread that owns a mutex object calls lock()
+/// on that object).
+/// @see recursive_mutex
+class mutex {
+ public:
+ /// Constructor.
+ mutex()
+#if defined(_TTHREAD_WIN32_)
+ : mAlreadyLocked(false)
+#endif
+ {
+#if defined(_TTHREAD_WIN32_)
+ InitializeCriticalSection(&mHandle);
+#else
+ pthread_mutex_init(&mHandle, NULL);
+#endif
+ }
+
+ /// Destructor.
+ ~mutex()
+ {
+#if defined(_TTHREAD_WIN32_)
+ DeleteCriticalSection(&mHandle);
+#else
+ pthread_mutex_destroy(&mHandle);
+#endif
+ }
+
+ /// Lock the mutex.
+ /// The method will block the calling thread until a lock on the mutex can
+ /// be obtained. The mutex remains locked until @c unlock() is called.
+ /// @see lock_guard
+ inline void lock()
+ {
+#if defined(_TTHREAD_WIN32_)
+ EnterCriticalSection(&mHandle);
+ while(mAlreadyLocked) Sleep(1000); // Simulate deadlock...
+ mAlreadyLocked = true;
+#else
+ pthread_mutex_lock(&mHandle);
+#endif
+ }
+
+ /// Try to lock the mutex.
+ /// The method will try to lock the mutex. If it fails, the function will
+ /// return immediately (non-blocking).
+ /// @return @c true if the lock was acquired, or @c false if the lock could
+ /// not be acquired.
+ inline bool try_lock()
+ {
+#if defined(_TTHREAD_WIN32_)
+ bool ret = (TryEnterCriticalSection(&mHandle) ? true : false);
+ if(ret && mAlreadyLocked)
+ {
+ LeaveCriticalSection(&mHandle);
+ ret = false;
+ }
+ return ret;
+#else
+ return (pthread_mutex_trylock(&mHandle) == 0) ? true : false;
+#endif
+ }
+
+ /// Unlock the mutex.
+ /// If any threads are waiting for the lock on this mutex, one of them will
+ /// be unblocked.
+ inline void unlock()
+ {
+#if defined(_TTHREAD_WIN32_)
+ mAlreadyLocked = false;
+ LeaveCriticalSection(&mHandle);
+#else
+ pthread_mutex_unlock(&mHandle);
+#endif
+ }
+
+ _TTHREAD_DISABLE_ASSIGNMENT(mutex)
+
+ private:
+#if defined(_TTHREAD_WIN32_)
+ CRITICAL_SECTION mHandle;
+ bool mAlreadyLocked;
+#else
+ pthread_mutex_t mHandle;
+#endif
+
+ friend class condition_variable;
+};
+
+/// Recursive mutex class.
+/// This is a mutual exclusion object for synchronizing access to shared
+/// memory areas for several threads. The mutex is recursive (i.e. a thread
+/// may lock the mutex several times, as long as it unlocks the mutex the same
+/// number of times).
+/// @see mutex
+class recursive_mutex {
+ public:
+ /// Constructor.
+ recursive_mutex()
+ {
+#if defined(_TTHREAD_WIN32_)
+ InitializeCriticalSection(&mHandle);
+#else
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&mHandle, &attr);
+#endif
+ }
+
+ /// Destructor.
+ ~recursive_mutex()
+ {
+#if defined(_TTHREAD_WIN32_)
+ DeleteCriticalSection(&mHandle);
+#else
+ pthread_mutex_destroy(&mHandle);
+#endif
+ }
+
+ /// Lock the mutex.
+ /// The method will block the calling thread until a lock on the mutex can
+ /// be obtained. The mutex remains locked until @c unlock() is called.
+ /// @see lock_guard
+ inline void lock()
+ {
+#if defined(_TTHREAD_WIN32_)
+ EnterCriticalSection(&mHandle);
+#else
+ pthread_mutex_lock(&mHandle);
+#endif
+ }
+
+ /// Try to lock the mutex.
+ /// The method will try to lock the mutex. If it fails, the function will
+ /// return immediately (non-blocking).
+ /// @return @c true if the lock was acquired, or @c false if the lock could
+ /// not be acquired.
+ inline bool try_lock()
+ {
+#if defined(_TTHREAD_WIN32_)
+ return TryEnterCriticalSection(&mHandle) ? true : false;
+#else
+ return (pthread_mutex_trylock(&mHandle) == 0) ? true : false;
+#endif
+ }
+
+ /// Unlock the mutex.
+ /// If any threads are waiting for the lock on this mutex, one of them will
+ /// be unblocked.
+ inline void unlock()
+ {
+#if defined(_TTHREAD_WIN32_)
+ LeaveCriticalSection(&mHandle);
+#else
+ pthread_mutex_unlock(&mHandle);
+#endif
+ }
+
+ _TTHREAD_DISABLE_ASSIGNMENT(recursive_mutex)
+
+ private:
+#if defined(_TTHREAD_WIN32_)
+ CRITICAL_SECTION mHandle;
+#else
+ pthread_mutex_t mHandle;
+#endif
+
+ friend class condition_variable;
+};
+
+/// Lock guard class.
+/// The constructor locks the mutex, and the destructor unlocks the mutex, so
+/// the mutex will automatically be unlocked when the lock guard goes out of
+/// scope. Example usage:
+/// @code
+/// mutex m;
+/// int counter;
+///
+/// void increment()
+/// {
+/// lock_guard<mutex> guard(m);
+/// ++ counter;
+/// }
+/// @endcode
+
+template <class T>
+class lock_guard {
+ public:
+ typedef T mutex_type;
+
+ lock_guard() : mMutex(0) {}
+
+ /// The constructor locks the mutex.
+ explicit lock_guard(mutex_type &aMutex)
+ {
+ mMutex = &aMutex;
+ mMutex->lock();
+ }
+
+ /// The destructor unlocks the mutex.
+ ~lock_guard()
+ {
+ if(mMutex)
+ mMutex->unlock();
+ }
+
+ private:
+ mutex_type * mMutex;
+};
+
+/// Condition variable class.
+/// This is a signalling object for synchronizing the execution flow for
+/// several threads. Example usage:
+/// @code
+/// // Shared data and associated mutex and condition variable objects
+/// int count;
+/// mutex m;
+/// condition_variable cond;
+///
+/// // Wait for the counter to reach a certain number
+/// void wait_counter(int targetCount)
+/// {
+/// lock_guard<mutex> guard(m);
+/// while(count < targetCount)
+/// cond.wait(m);
+/// }
+///
+/// // Increment the counter, and notify waiting threads
+/// void increment()
+/// {
+/// lock_guard<mutex> guard(m);
+/// ++ count;
+/// cond.notify_all();
+/// }
+/// @endcode
+class condition_variable {
+ public:
+ /// Constructor.
+#if defined(_TTHREAD_WIN32_)
+ condition_variable();
+#else
+ condition_variable()
+ {
+ pthread_cond_init(&mHandle, NULL);
+ }
+#endif
+
+ /// Destructor.
+#if defined(_TTHREAD_WIN32_)
+ ~condition_variable();
+#else
+ ~condition_variable()
+ {
+ pthread_cond_destroy(&mHandle);
+ }
+#endif
+
+ /// Wait for the condition.
+ /// The function will block the calling thread until the condition variable
+ /// is woken by @c notify_one(), @c notify_all() or a spurious wake up.
+ /// @param[in] aMutex A mutex that will be unlocked when the wait operation
+ /// starts, an locked again as soon as the wait operation is finished.
+ template <class _mutexT>
+ inline void wait(_mutexT &aMutex)
+ {
+#if defined(_TTHREAD_WIN32_)
+ // Increment number of waiters
+ EnterCriticalSection(&mWaitersCountLock);
+ ++ mWaitersCount;
+ LeaveCriticalSection(&mWaitersCountLock);
+
+ // Release the mutex while waiting for the condition (will decrease
+ // the number of waiters when done)...
+ aMutex.unlock();
+ _wait();
+ aMutex.lock();
+#else
+ pthread_cond_wait(&mHandle, &aMutex.mHandle);
+#endif
+ }
+
+ /// Notify one thread that is waiting for the condition.
+ /// If at least one thread is blocked waiting for this condition variable,
+ /// one will be woken up.
+ /// @note Only threads that started waiting prior to this call will be
+ /// woken up.
+#if defined(_TTHREAD_WIN32_)
+ void notify_one();
+#else
+ inline void notify_one()
+ {
+ pthread_cond_signal(&mHandle);
+ }
+#endif
+
+ /// Notify all threads that are waiting for the condition.
+ /// All threads that are blocked waiting for this condition variable will
+ /// be woken up.
+ /// @note Only threads that started waiting prior to this call will be
+ /// woken up.
+#if defined(_TTHREAD_WIN32_)
+ void notify_all();
+#else
+ inline void notify_all()
+ {
+ pthread_cond_broadcast(&mHandle);
+ }
+#endif
+
+ _TTHREAD_DISABLE_ASSIGNMENT(condition_variable)
+
+ private:
+#if defined(_TTHREAD_WIN32_)
+ void _wait();
+ HANDLE mEvents[2]; ///< Signal and broadcast event HANDLEs.
+ unsigned int mWaitersCount; ///< Count of the number of waiters.
+ CRITICAL_SECTION mWaitersCountLock; ///< Serialize access to mWaitersCount.
+#else
+ pthread_cond_t mHandle;
+#endif
+};
+
+
+/// Thread class.
+class thread {
+ public:
+#if defined(_TTHREAD_WIN32_)
+ typedef HANDLE native_handle_type;
+#else
+ typedef pthread_t native_handle_type;
+#endif
+
+ class id;
+
+ /// Default constructor.
+ /// Construct a @c thread object without an associated thread of execution
+ /// (i.e. non-joinable).
+ thread() : mHandle(0), mNotAThread(true)
+#if defined(_TTHREAD_WIN32_)
+ , mWin32ThreadID(0)
+#endif
+ {}
+
+ /// Thread starting constructor.
+ /// Construct a @c thread object with a new thread of execution.
+ /// @param[in] aFunction A function pointer to a function of type:
+ /// <tt>void fun(void * arg)</tt>
+ /// @param[in] aArg Argument to the thread function.
+ /// @note This constructor is not fully compatible with the standard C++
+ /// thread class. It is more similar to the pthread_create() (POSIX) and
+ /// CreateThread() (Windows) functions.
+ thread(void (*aFunction)(void *), void * aArg);
+
+ /// Destructor.
+ /// @note If the thread is joinable upon destruction, @c std::terminate()
+ /// will be called, which terminates the process. It is always wise to do
+ /// @c join() before deleting a thread object.
+ ~thread();
+
+ /// Wait for the thread to finish (join execution flows).
+ /// After calling @c join(), the thread object is no longer associated with
+ /// a thread of execution (i.e. it is not joinable, and you may not join
+ /// with it nor detach from it).
+ void join();
+
+ /// Check if the thread is joinable.
+ /// A thread object is joinable if it has an associated thread of execution.
+ bool joinable() const;
+
+ /// Detach from the thread.
+ /// After calling @c detach(), the thread object is no longer assicated with
+ /// a thread of execution (i.e. it is not joinable). The thread continues
+ /// execution without the calling thread blocking, and when the thread
+ /// ends execution, any owned resources are released.
+ void detach();
+
+ /// Return the thread ID of a thread object.
+ id get_id() const;
+
+ /// Get the native handle for this thread.
+ /// @note Under Windows, this is a @c HANDLE, and under POSIX systems, this
+ /// is a @c pthread_t.
+ inline native_handle_type native_handle()
+ {
+ return mHandle;
+ }
+
+ /// Determine the number of threads which can possibly execute concurrently.
+ /// This function is useful for determining the optimal number of threads to
+ /// use for a task.
+ /// @return The number of hardware thread contexts in the system.
+ /// @note If this value is not defined, the function returns zero (0).
+ static unsigned hardware_concurrency();
+
+ _TTHREAD_DISABLE_ASSIGNMENT(thread)
+
+ private:
+ native_handle_type mHandle; ///< Thread handle.
+ mutable mutex mDataMutex; ///< Serializer for access to the thread private data.
+ bool mNotAThread; ///< True if this object is not a thread of execution.
+#if defined(_TTHREAD_WIN32_)
+ unsigned int mWin32ThreadID; ///< Unique thread ID (filled out by _beginthreadex).
+#endif
+
+ // This is the internal thread wrapper function.
+#if defined(_TTHREAD_WIN32_)
+ static unsigned WINAPI wrapper_function(void * aArg);
+#else
+ static void * wrapper_function(void * aArg);
+#endif
+};
+
+/// Thread ID.
+/// The thread ID is a unique identifier for each thread.
+/// @see thread::get_id()
+class thread::id {
+ public:
+ /// Default constructor.
+ /// The default constructed ID is that of thread without a thread of
+ /// execution.
+ id() : mId(0) {};
+
+ id(unsigned long int aId) : mId(aId) {};
+
+ id(const id& aId) : mId(aId.mId) {};
+
+ inline id & operator=(const id &aId)
+ {
+ mId = aId.mId;
+ return *this;
+ }
+
+ inline friend bool operator==(const id &aId1, const id &aId2)
+ {
+ return (aId1.mId == aId2.mId);
+ }
+
+ inline friend bool operator!=(const id &aId1, const id &aId2)
+ {
+ return (aId1.mId != aId2.mId);
+ }
+
+ inline friend bool operator<=(const id &aId1, const id &aId2)
+ {
+ return (aId1.mId <= aId2.mId);
+ }
+
+ inline friend bool operator<(const id &aId1, const id &aId2)
+ {
+ return (aId1.mId < aId2.mId);
+ }
+
+ inline friend bool operator>=(const id &aId1, const id &aId2)
+ {
+ return (aId1.mId >= aId2.mId);
+ }
+
+ inline friend bool operator>(const id &aId1, const id &aId2)
+ {
+ return (aId1.mId > aId2.mId);
+ }
+
+ inline friend std::ostream& operator <<(std::ostream &os, const id &obj)
+ {
+ os << obj.mId;
+ return os;
+ }
+
+ private:
+ unsigned long int mId;
+};
+
+
+// Related to <ratio> - minimal to be able to support chrono.
+typedef long long __intmax_t;
+
+/// Minimal implementation of the @c ratio class. This class provides enough
+/// functionality to implement some basic @c chrono classes.
+template <__intmax_t N, __intmax_t D = 1> class ratio {
+ public:
+ static double _as_double() { return double(N) / double(D); }
+};
+
+/// Minimal implementation of the @c chrono namespace.
+/// The @c chrono namespace provides types for specifying time intervals.
+namespace chrono {
+ /// Duration template class. This class provides enough functionality to
+ /// implement @c this_thread::sleep_for().
+ template <class _Rep, class _Period = ratio<1> > class duration {
+ private:
+ _Rep rep_;
+ public:
+ typedef _Rep rep;
+ typedef _Period period;
+
+ /// Construct a duration object with the given duration.
+ template <class _Rep2>
+ explicit duration(const _Rep2& r) : rep_(r) {};
+
+ /// Return the value of the duration object.
+ rep count() const
+ {
+ return rep_;
+ }
+ };
+
+ // Standard duration types.
+ typedef duration<__intmax_t, ratio<1, 1000000000> > nanoseconds; ///< Duration with the unit nanoseconds.
+ typedef duration<__intmax_t, ratio<1, 1000000> > microseconds; ///< Duration with the unit microseconds.
+ typedef duration<__intmax_t, ratio<1, 1000> > milliseconds; ///< Duration with the unit milliseconds.
+ typedef duration<__intmax_t> seconds; ///< Duration with the unit seconds.
+ typedef duration<__intmax_t, ratio<60> > minutes; ///< Duration with the unit minutes.
+ typedef duration<__intmax_t, ratio<3600> > hours; ///< Duration with the unit hours.
+}
+
+/// The namespace @c this_thread provides methods for dealing with the
+/// calling thread.
+namespace this_thread {
+ /// Return the thread ID of the calling thread.
+ thread::id get_id();
+
+ /// Yield execution to another thread.
+ /// Offers the operating system the opportunity to schedule another thread
+ /// that is ready to run on the current processor.
+ inline void yield()
+ {
+#if defined(_TTHREAD_WIN32_)
+ Sleep(0);
+#else
+ sched_yield();
+#endif
+ }
+
+ /// Blocks the calling thread for a period of time.
+ /// @param[in] aTime Minimum time to put the thread to sleep.
+ /// Example usage:
+ /// @code
+ /// // Sleep for 100 milliseconds
+ /// this_thread::sleep_for(chrono::milliseconds(100));
+ /// @endcode
+ /// @note Supported duration types are: nanoseconds, microseconds,
+ /// milliseconds, seconds, minutes and hours.
+ template <class _Rep, class _Period> void sleep_for(const chrono::duration<_Rep, _Period>& aTime)
+ {
+#if defined(_TTHREAD_WIN32_)
+ Sleep(int(double(aTime.count()) * (1000.0 * _Period::_as_double()) + 0.5));
+#else
+ usleep(int(double(aTime.count()) * (1000000.0 * _Period::_as_double()) + 0.5));
+#endif
+ }
+}
+
+}
+
+// Define/macro cleanup
+#undef _TTHREAD_DISABLE_ASSIGNMENT
+
+#endif // _TINYTHREAD_H_
diff --git a/src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp
new file mode 100644
index 0000000..e31438a
--- /dev/null
+++ b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp
@@ -0,0 +1,289 @@
+#include "uscxml/datamodel/ecmascript/v8/V8DataModel.h"
+#include "uscxml/Message.h"
+
+namespace uscxml {
+
+V8DataModel::V8DataModel() {
+// _contexts.push_back(v8::Context::New());
+}
+
+DataModel* V8DataModel::create(Interpreter* interpreter) {
+ V8DataModel* dm = new V8DataModel();
+ dm->_interpreter = interpreter;
+ v8::Locker locker;
+ v8::HandleScope scope;
+
+ // see http://stackoverflow.com/questions/3171418/v8-functiontemplate-class-instance
+ dm->_globalTemplate = v8::Persistent<v8::ObjectTemplate>(v8::ObjectTemplate::New());
+ dm->_globalTemplate->Set(v8::String::New("In"), v8::FunctionTemplate::New(jsIn, v8::External::New(reinterpret_cast<void*>(this))));
+
+ dm->_contexts.push_back(v8::Context::New(0, _globalTemplate));
+ dm->setName(interpreter->getName());
+ dm->setSessionId(interpreter->getSessionId());
+ dm->eval("_ioprocessors = {};");
+ return dm;
+}
+
+void V8DataModel::setSessionId(const std::string& sessionId) {
+ _sessionId = sessionId;
+ v8::Locker locker;
+ v8::HandleScope handleScope;
+ v8::Context::Scope contextScope(_contexts.front());
+ v8::Handle<v8::Object> global = _contexts.front()->Global();
+
+ global->Set(v8::String::New("_sessionid"), v8::String::New(sessionId.c_str()));
+}
+
+void V8DataModel::setName(const std::string& name) {
+ _name = name;
+ v8::HandleScope handleScope;
+ v8::Context::Scope contextScope(_contexts.front());
+ v8::Handle<v8::Object> global = _contexts.front()->Global();
+
+ global->Set(v8::String::New("_name"), v8::String::New(name.c_str()));
+}
+
+V8DataModel::~V8DataModel() {
+ while(_contexts.size() > 0) {
+ _contexts.back().Dispose();
+ _contexts.pop_back();
+ }
+}
+
+void V8DataModel::pushContext() {
+ _contexts.push_back(_contexts.back().New(_contexts.back()));
+}
+
+void V8DataModel::popContext() {
+ if (_contexts.size() > 1) {
+ _contexts.back().Dispose();
+ _contexts.pop_back();
+ }
+}
+
+void V8DataModel::initialize() {
+}
+
+void V8DataModel::setEvent(Event& event) {
+ _event = event;
+ v8::Locker locker;
+ v8::HandleScope handleScope;
+ v8::Context::Scope contextScope(_contexts.front());
+ v8::Handle<v8::Object> global = _contexts.front()->Global();
+
+ // this is unfortunate - can't we store the template in the object?
+ if (_eventTemplate.IsEmpty()) {
+ v8::Handle<v8::ObjectTemplate> localEventTemplate = v8::ObjectTemplate::New();
+ localEventTemplate->SetInternalFieldCount(1); // we only have a single C++ object
+ localEventTemplate->SetAccessor(v8::String::New("name"), V8DataModel::jsGetEventName);
+ localEventTemplate->SetAccessor(v8::String::New("type"), V8DataModel::jsGetEventType);
+ localEventTemplate->SetAccessor(v8::String::New("sendid"), V8DataModel::jsGetEventSendId);
+ localEventTemplate->SetAccessor(v8::String::New("origin"), V8DataModel::jsGetEventOrigin);
+ localEventTemplate->SetAccessor(v8::String::New("origintype"), V8DataModel::jsGetEventOriginType);
+ localEventTemplate->SetAccessor(v8::String::New("invokeid"), V8DataModel::jsGetEventInvokeId);
+ _eventTemplate = v8::Persistent<v8::ObjectTemplate>::New(localEventTemplate);
+ }
+
+ assert(_eventTemplate->InternalFieldCount() == 1);
+ v8::Handle<v8::Object> eventJS = _eventTemplate->NewInstance();
+ eventJS->SetInternalField(0, v8::External::New(&event));
+
+ eventJS->Set(v8::String::New("data"), getDataAsValue(event)); // set data part of _event
+ global->Set(v8::String::New("_event"), eventJS);
+}
+
+void V8DataModel::setData(const std::string& key, Data& data) {
+ v8::Locker locker;
+ v8::HandleScope handleScope;
+ v8::Context::Scope contextScope(_contexts.front());
+ v8::Handle<v8::Object> global = _contexts.front()->Global();
+ global->Set(v8::String::New(key.c_str()), getDataAsValue(data));
+}
+
+v8::Handle<v8::Value> V8DataModel::getDataAsValue(Data& data) {
+ if (data.compound.size() > 0) {
+ v8::Handle<v8::Object> value = v8::Array::New();
+ std::map<std::string, Data>::iterator compoundIter = data.compound.begin();
+ while(compoundIter != data.compound.end()) {
+ value->Set(v8::String::New(compoundIter->first.c_str()), getDataAsValue(compoundIter->second));
+ compoundIter++;
+ }
+ return value;
+ }
+ if (data.array.size() > 0) {
+ v8::Handle<v8::Object> value = v8::Array::New();
+ std::vector<Data>::iterator arrayIter = data.array.begin();
+ uint32_t index = 0;
+ while(arrayIter != data.array.end()) {
+ value->Set(index++, getDataAsValue(*arrayIter));
+ arrayIter++;
+ }
+ return value;
+ }
+ if (data.type == Data::VERBATIM) {
+ return v8::String::New(data.atom.c_str());
+ } else {
+ return evalAsValue(data.atom);
+ }
+}
+
+v8::Handle<v8::Value> V8DataModel::jsIn(const v8::Arguments& args) {
+ V8DataModel* THIS = static_cast<V8DataModel*>(v8::External::Unwrap(args.Data()));
+ for (unsigned int i = 0; i < args.Length(); i++) {
+ if (args[i]->IsString()) {
+ std::string stateName(*v8::String::AsciiValue(args[i]->ToString()));
+ if (Interpreter::isMember(THIS->_interpreter->getState(stateName), THIS->_interpreter->getConfiguration())) {
+ continue;
+ }
+ }
+ return v8::Boolean::New(false);
+ }
+ return v8::Boolean::New(true);
+}
+
+v8::Handle<v8::Value> V8DataModel::jsGetEventName(v8::Local<v8::String> property,
+ const v8::AccessorInfo &info) {
+ Event* event = static_cast<Event*>(v8::Local<v8::External>::Cast(info.Holder()->GetInternalField(0))->Value());
+ return v8::String::New(event->name.c_str());
+}
+
+v8::Handle<v8::Value> V8DataModel::jsGetEventType(v8::Local<v8::String> property,
+ const v8::AccessorInfo &info) {
+ Event* event = static_cast<Event*>(v8::Local<v8::External>::Cast(info.Holder()->GetInternalField(0))->Value());
+ switch (event->type) {
+ case Event::PLATFORM:
+ return v8::String::New("platform");
+ break;
+ case Event::INTERNAL:
+ return v8::String::New("internal");
+ break;
+ case Event::EXTERNAL:
+ return v8::String::New("external");
+ break;
+ default:
+ return v8::String::New("");
+ break;
+ }
+}
+
+v8::Handle<v8::Value> V8DataModel::jsGetEventSendId(v8::Local<v8::String> property,
+ const v8::AccessorInfo &info) {
+ Event* event = static_cast<Event*>(v8::Local<v8::External>::Cast(info.Holder()->GetInternalField(0))->Value());
+ return v8::String::New(event->sendid.c_str());
+
+}
+
+v8::Handle<v8::Value> V8DataModel::jsGetEventOrigin(v8::Local<v8::String> property,
+ const v8::AccessorInfo &info) {
+ Event* event = static_cast<Event*>(v8::Local<v8::External>::Cast(info.Holder()->GetInternalField(0))->Value());
+ return v8::String::New(event->origin.c_str());
+}
+
+v8::Handle<v8::Value> V8DataModel::jsGetEventOriginType(v8::Local<v8::String> property,
+ const v8::AccessorInfo &info) {
+ Event* event = static_cast<Event*>(v8::Local<v8::External>::Cast(info.Holder()->GetInternalField(0))->Value());
+ return v8::String::New(event->origintype.c_str());
+}
+
+v8::Handle<v8::Value> V8DataModel::jsGetEventInvokeId(v8::Local<v8::String> property,
+ const v8::AccessorInfo &info) {
+ Event* event = static_cast<Event*>(v8::Local<v8::External>::Cast(info.Holder()->GetInternalField(0))->Value());
+ return v8::String::New(event->invokeid.c_str());
+}
+
+bool V8DataModel::validate(const std::string& location, const std::string& schema) {
+ return true;
+}
+
+uint32_t V8DataModel::getLength(const std::string& expr) {
+ v8::Locker locker;
+ v8::HandleScope handleScope;
+ v8::Context::Scope contextScope(_contexts.back());
+ v8::Handle<v8::Array> result = evalAsValue(expr).As<v8::Array>();
+ return result->Length();
+}
+
+void V8DataModel::eval(const std::string& expr) {
+ v8::Locker locker;
+ v8::HandleScope handleScope;
+ v8::Context::Scope contextScope(_contexts.back());
+ evalAsValue(expr);
+}
+
+bool V8DataModel::evalAsBool(const std::string& expr) {
+ v8::Locker locker;
+ v8::HandleScope handleScope;
+ v8::Context::Scope contextScope(_contexts.back());
+ v8::Handle<v8::Value> result = evalAsValue(expr);
+ return(result->ToBoolean()->BooleanValue());
+}
+
+std::string V8DataModel::evalAsString(const std::string& expr) {
+ v8::Locker locker;
+ v8::HandleScope handleScope;
+ v8::Context::Scope contextScope(_contexts.back());
+ v8::Handle<v8::Value> result = evalAsValue(expr);
+ v8::String::AsciiValue data(result->ToString());
+ return std::string(*data);
+}
+
+void V8DataModel::assign(const std::string& location, const std::string& expr) {
+ v8::Locker locker;
+ v8::HandleScope handleScope;
+ v8::Context::Scope contextScope(_contexts.back());
+ evalAsValue((location + " = " + expr).c_str());
+}
+
+v8::Handle<v8::Value> V8DataModel::evalAsValue(const std::string& expr) {
+ v8::TryCatch tryCatch;
+ v8::Handle<v8::String> source = v8::String::New(expr.c_str());
+ v8::Handle<v8::Script> script = v8::Script::Compile(source);
+
+ v8::Handle<v8::Value> result;
+ if (!script.IsEmpty())
+ result = script->Run();
+
+ if (script.IsEmpty() || result.IsEmpty()) {
+ // throw an exception
+ assert(tryCatch.HasCaught());
+ Event exceptionEvent;
+ exceptionEvent.name = "error.execution";
+
+ std::string exceptionString(*v8::String::AsciiValue(tryCatch.Exception()));
+ exceptionEvent.compound["exception"] = Data(exceptionString, Data::VERBATIM);;
+
+ v8::Handle<v8::Message> message = tryCatch.Message();
+ if (!message.IsEmpty()) {
+ std::string filename(*v8::String::AsciiValue(message->GetScriptResourceName()));
+ exceptionEvent.compound["filename"] = Data(filename, Data::VERBATIM);
+
+ std::string sourceLine(*v8::String::AsciiValue(message->GetSourceLine()));
+ exceptionEvent.compound["sourceline"] = Data(sourceLine, Data::VERBATIM);
+
+ std::stringstream ssLineNumber;
+ int lineNumber = message->GetLineNumber();
+ ssLineNumber << lineNumber;
+ exceptionEvent.compound["linenumber"] = Data(ssLineNumber.str());
+
+ int startColumn = message->GetStartColumn();
+ int endColumn = message->GetEndColumn();
+ std::stringstream ssUnderline;
+ for (int i = 0; i < startColumn; i++)
+ ssUnderline << " ";
+ for (int i = startColumn; i < endColumn; i++)
+ ssUnderline << "^";
+ exceptionEvent.compound["sourcemark"] = Data(ssUnderline.str(), Data::VERBATIM);
+
+ std::string stackTrace(*v8::String::AsciiValue(tryCatch.StackTrace()));
+ exceptionEvent.compound["stacktrace"] = Data(stackTrace, Data::VERBATIM);
+
+ }
+
+ _interpreter->receiveInternal(exceptionEvent);
+ throw(exceptionEvent);
+ }
+
+ return result;
+}
+
+} \ No newline at end of file
diff --git a/src/uscxml/datamodel/ecmascript/v8/V8DataModel.h b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.h
new file mode 100644
index 0000000..7522679
--- /dev/null
+++ b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.h
@@ -0,0 +1,73 @@
+#ifndef V8DATAMODEL_H_KN8TWG0V
+#define V8DATAMODEL_H_KN8TWG0V
+
+#include "uscxml/Interpreter.h"
+#include <list>
+#include <v8.h>
+
+namespace uscxml {
+ class Event;
+ class Data;
+}
+
+namespace uscxml {
+
+class V8DataModel : public DataModel {
+public:
+ V8DataModel();
+ virtual ~V8DataModel();
+ virtual DataModel* create(Interpreter* interpreter);
+
+ virtual void initialize();
+ virtual void setSessionId(const std::string& sessionId);
+ virtual void setName(const std::string& name);
+ virtual void setEvent(Event& event);
+ virtual void setData(const std::string& key, Data& event);
+ virtual v8::Handle<v8::Value> getDataAsValue(Data& data);
+
+ virtual bool validate(const std::string& location, const std::string& schema);
+
+ virtual uint32_t getLength(const std::string& expr);
+ virtual void pushContext();
+ virtual void popContext();
+
+ virtual void eval(const std::string& expr);
+ virtual void assign(const std::string& location, const std::string& expr);
+
+ virtual std::string evalAsString(const std::string& expr);
+ virtual bool evalAsBool(const std::string& expr);
+
+ static v8::Handle<v8::Value> jsGetEventName(v8::Local<v8::String> property,
+ const v8::AccessorInfo &info);
+ static v8::Handle<v8::Value> jsGetEventType(v8::Local<v8::String> property,
+ const v8::AccessorInfo &info);
+ static v8::Handle<v8::Value> jsGetEventSendId(v8::Local<v8::String> property,
+ const v8::AccessorInfo &info);
+ static v8::Handle<v8::Value> jsGetEventOrigin(v8::Local<v8::String> property,
+ const v8::AccessorInfo &info);
+ static v8::Handle<v8::Value> jsGetEventOriginType(v8::Local<v8::String> property,
+ const v8::AccessorInfo &info);
+ static v8::Handle<v8::Value> jsGetEventInvokeId(v8::Local<v8::String> property,
+ const v8::AccessorInfo &info);
+
+ static v8::Handle<v8::Value> jsIn(const v8::Arguments& args);
+
+
+protected:
+ std::list<v8::Persistent<v8::Context> > _contexts;
+ Interpreter* _interpreter;
+
+ std::string _sessionId;
+ std::string _name;
+
+ Event _event;
+ v8::Persistent<v8::ObjectTemplate> _globalTemplate;
+ v8::Persistent<v8::ObjectTemplate> _eventTemplate;
+
+ v8::Handle<v8::Value> evalAsValue(const std::string& expr);
+
+};
+
+}
+
+#endif /* end of include guard: V8DATAMODEL_H_KN8TWG0V */
diff --git a/src/uscxml/ioprocessor/basichttp/README.md b/src/uscxml/ioprocessor/basichttp/README.md
new file mode 100644
index 0000000..de89944
--- /dev/null
+++ b/src/uscxml/ioprocessor/basichttp/README.md
@@ -0,0 +1,2 @@
+Only the libevent basichttp ioprocessor is supported. Mongoose seemed somewhat
+unmaintained and pion comes with too many dependencies. \ No newline at end of file
diff --git a/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp b/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp
new file mode 100644
index 0000000..c06c7e8
--- /dev/null
+++ b/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp
@@ -0,0 +1,361 @@
+#include "uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h"
+#include "uscxml/Message.h"
+#include <iostream>
+#include <event2/dns.h>
+#include <event2/buffer.h>
+#include <event2/keyvalq_struct.h>
+
+#include <string.h>
+
+#include <io/uri.hpp>
+#include <glog/logging.h>
+
+#include <netdb.h>
+#include <arpa/inet.h>
+
+namespace uscxml {
+namespace io {
+namespace libevent {
+
+// see http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor
+
+EventIOProcessor::EventIOProcessor() {
+}
+
+EventIOProcessor::~EventIOProcessor() {
+ _eventQueue.stop();
+ EventIOServer* httpServer = EventIOServer::getInstance();
+ httpServer->unregisterProcessor(this);
+}
+
+IOProcessor* EventIOProcessor::create(Interpreter* interpreter) {
+ EventIOProcessor* io = new EventIOProcessor();
+ io->_interpreter = interpreter;
+
+ io->_dns = evdns_base_new(io->_eventQueue._eventLoop, 1);
+ assert(io->_dns);
+ assert(evdns_base_count_nameservers(io->_dns) > 0);
+
+ // register at http server
+ EventIOServer* httpServer = EventIOServer::getInstance();
+ httpServer->registerProcessor(io);
+
+ io->start();
+ return io;
+}
+
+void EventIOProcessor::start() {
+ _eventQueue.start();
+}
+
+void EventIOProcessor::send(SendRequest& req) {
+ // I cant figure out how to copy the reference into the struct :(
+ _sendData[req.sendid].req = req;
+ _sendData[req.sendid].ioProcessor = this;
+
+ if (req.delayMs > 0) {
+ LOG(INFO) << "Enqueing HTTP send request";
+ _eventQueue.addEvent(req.sendid, EventIOProcessor::httpMakeSendReq, req.delayMs, &_sendData[req.sendid]);
+ } else {
+ LOG(INFO) << "Sending HTTP send request";
+ EventIOProcessor::httpMakeSendReq(&_sendData[req.sendid], req.sendid);
+ }
+}
+
+void EventIOProcessor::httpMakeSendReq(void* userdata, std::string eventName) {
+ SendData* sendData = ((SendData*)userdata);
+ EventIOProcessor* THIS = sendData->ioProcessor;
+ int err = 0;
+ char uriBuf[1024];
+
+ struct evhttp_uri* targetURI = evhttp_uri_parse(sendData->req.target.c_str());
+ if (evhttp_uri_get_port(targetURI) == 0)
+ evhttp_uri_set_port(targetURI, 80);
+ const char* hostName = evhttp_uri_get_host(targetURI);
+
+ // use synchronous dns resolving for multicast dns
+ if(strlen(hostName) >= strlen(".local")) {
+ if(strcmp(hostName + strlen(hostName) - strlen(".local"), ".local") == 0) {
+ evhttp_uri_set_host(targetURI, EventIOServer::syncResolve(hostName).c_str());
+ }
+ }
+ evhttp_uri_join(targetURI, uriBuf, 1024);
+
+ LOG(INFO) << "URI for send request: " << uriBuf << std::endl;
+
+ std::stringstream ssEndPoint;
+ ssEndPoint << evhttp_uri_get_host(targetURI) << ":" << evhttp_uri_get_port(targetURI);
+ std::string endPoint = ssEndPoint.str();
+
+ std::stringstream ssLocalURI;
+ ssLocalURI << evhttp_uri_get_path(targetURI) << evhttp_uri_get_fragment(targetURI);
+ std::string localURI = ssLocalURI.str();
+
+ if (THIS->_httpConnections.find(endPoint) == THIS->_httpConnections.end())
+ THIS->_httpConnections[endPoint] = evhttp_connection_base_new(THIS->_eventQueue._eventLoop, THIS->_dns, evhttp_uri_get_host(targetURI), evhttp_uri_get_port(targetURI));
+
+ struct evhttp_connection* httpConn = THIS->_httpConnections[endPoint];
+ struct evhttp_request* httpReq = evhttp_request_new(EventIOProcessor::httpSendReqDone, userdata);
+
+#if 0
+ // event name
+ if (sendData->req.event.size() > 0) {
+ evhttp_add_header(evhttp_request_get_output_headers(httpReq), "_scxmleventname", evhttp_encode_uri(sendData->req.event.c_str()));
+ }
+
+ // event namelist
+ if (sendData->req.namelist.size() > 0) {
+ std::map<std::string, std::string>::iterator namelistIter = sendData->req.namelist.begin();
+ while (namelistIter != sendData->req.namelist.end()) {
+ evhttp_add_header(evhttp_request_get_output_headers(httpReq),
+ namelistIter->first.c_str(),
+ evhttp_encode_uri(namelistIter->second.c_str()));
+ namelistIter++;
+ }
+ }
+
+ // event params
+ if (sendData->req.params.size() > 0) {
+ std::map<std::string, std::string>::iterator paramIter = sendData->req.params.begin();
+ while (paramIter != sendData->req.params.end()) {
+ evhttp_add_header(evhttp_request_get_output_headers(httpReq),
+ paramIter->first.c_str(),
+ evhttp_encode_uri(paramIter->second.c_str()));
+ paramIter++;
+ }
+ }
+
+ // content
+ if (sendData->req.content.size() > 0)
+ evbuffer_add(evhttp_request_get_output_buffer(httpReq), sendData->req.content.c_str(), sendData->req.content.size());
+#endif
+
+ evhttp_add_header(evhttp_request_get_output_headers(httpReq), "_scxmleventstruct", evhttp_encode_uri(sendData->req.toXMLString().c_str()));
+
+
+ THIS->_httpRequests[sendData->req.sendid] = httpReq;
+ err = evhttp_make_request(httpConn,
+ httpReq,
+ EVHTTP_REQ_POST, localURI.c_str());
+ if (err) {
+ LOG(ERROR) << "Could not make http request to " << sendData->req.target;
+ }
+}
+
+void EventIOProcessor::httpRecvReq(struct evhttp_request *req, void *arg) {
+
+ const char *cmdtype;
+ struct evkeyvalq *headers;
+ struct evkeyval *header;
+ struct evbuffer *buf;
+
+ switch (evhttp_request_get_command(req)) {
+ case EVHTTP_REQ_GET: cmdtype = "GET"; break;
+ case EVHTTP_REQ_POST: cmdtype = "POST"; break;
+ case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break;
+ case EVHTTP_REQ_PUT: cmdtype = "PUT"; break;
+ case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break;
+ case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break;
+ case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break;
+ case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break;
+ case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break;
+ default: cmdtype = "unknown"; break;
+ }
+
+ Event reqEvent;
+ reqEvent.type = Event::EXTERNAL;
+
+ // map headers to event structure
+ headers = evhttp_request_get_input_headers(req);
+ for (header = headers->tqh_first; header;
+ header = header->next.tqe_next) {
+// std::cout << "Header: " << header->key << std::endl;
+// std::cout << "Value: " << evhttp_decode_uri(header->value) << std::endl;
+ if (boost::iequals("_scxmleventstruct", header->key)) {
+ reqEvent = Event::fromXML(evhttp_decode_uri(header->value));
+ break;
+ } else if (boost::iequals("_scxmleventname", header->key)) {
+ reqEvent.name = evhttp_decode_uri(header->value);
+ } else {
+ reqEvent.compound[header->key] = Data(evhttp_decode_uri(header->value), Data::VERBATIM);
+ }
+ }
+
+ // get content into event
+ std::string content;
+ buf = evhttp_request_get_input_buffer(req);
+ while (evbuffer_get_length(buf)) {
+ int n;
+ char cbuf[128];
+ n = evbuffer_remove(buf, cbuf, sizeof(buf)-1);
+ if (n > 0) {
+ content.append(cbuf, n);
+ }
+ }
+ reqEvent.compound["content"] = Data(content, Data::VERBATIM);
+
+ EventIOProcessor* THIS = (EventIOProcessor*)arg;
+ THIS->_interpreter->receive(reqEvent);
+
+ evhttp_send_reply(req, 200, "OK", NULL);
+}
+
+void EventIOProcessor::httpSendReqDone(struct evhttp_request *req, void *cb_arg) {
+ if (req) {
+ LOG(INFO) << "got return code " << evhttp_request_get_response_code(req) << std::endl;
+ }
+}
+
+void EventIOProcessor::invoke(InvokeRequest& req) {
+
+}
+void EventIOProcessor::cancel(const std::string sendId) {
+
+}
+
+EventIOServer::EventIOServer(unsigned short port) {
+ _port = port;
+ _base = event_base_new();
+ _http = evhttp_new(_base);
+ _handle = NULL;
+ while((_handle = evhttp_bind_socket_with_handle(_http, INADDR_ANY, _port)) == NULL) {
+ _port++;
+ }
+ determineAddress();
+}
+
+EventIOServer::~EventIOServer() {
+}
+
+EventIOServer* EventIOServer::_instance = NULL;
+tthread::recursive_mutex EventIOServer::_instanceMutex;
+
+EventIOServer* EventIOServer::getInstance() {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_instanceMutex);
+ if (_instance == NULL) {
+ _instance = new EventIOServer(8080);
+ _instance->start();
+ }
+ return _instance;
+}
+
+void EventIOServer::registerProcessor(EventIOProcessor* processor) {
+ EventIOServer* THIS = getInstance();
+ tthread::lock_guard<tthread::recursive_mutex> lock(THIS->_mutex);
+
+ /**
+ * Determine path for interpreter.
+ *
+ * If the interpreter has a name and it is not yet taken, choose it as the path
+ * for requests. If the interpreters name path is already taken, append digits
+ * until we have an available path.
+ *
+ * If the interpreter does not specify a name, take its sessionid.
+ */
+
+ std::string path = processor->_interpreter->getName();
+ if (path.size() == 0) {
+ path = processor->_interpreter->getSessionId();
+ }
+ assert(path.size() > 0);
+
+ std::stringstream actualPath(path);
+ int i = 1;
+ while(THIS->_processors.find(actualPath.str()) != THIS->_processors.end()) {
+ actualPath.str(std::string());
+ actualPath.clear();
+ actualPath << path << ++i;
+ }
+
+ std::stringstream processorURL;
+ processorURL << "http://" << THIS->_address << ":" << THIS->_port << "/" << actualPath.str();
+
+ THIS->_processors[actualPath.str()] = processor;
+ processor->setURL(processorURL.str());
+
+ evhttp_set_cb(THIS->_http, ("/" + actualPath.str()).c_str(), EventIOProcessor::httpRecvReq, processor);
+// evhttp_set_cb(THIS->_http, "/", EventIOProcessor::httpRecvReq, processor);
+// evhttp_set_gencb(THIS->_http, EventIOProcessor::httpRecvReq, NULL);
+}
+
+void EventIOServer::unregisterProcessor(EventIOProcessor* processor) {
+ EventIOServer* THIS = getInstance();
+ tthread::lock_guard<tthread::recursive_mutex> lock(THIS->_mutex);
+ evhttp_del_cb(THIS->_http, processor->getURL().c_str());
+}
+
+void EventIOServer::start() {
+ _isRunning = true;
+ _thread = new tthread::thread(EventIOServer::run, this);
+}
+
+void EventIOServer::run(void* instance) {
+ EventIOServer* THIS = (EventIOServer*)instance;
+ while(THIS->_isRunning) {
+ LOG(INFO) << "Dispatching HTTP Server" << std::endl;
+ event_base_dispatch(THIS->_base);
+ }
+ LOG(INFO) << "HTTP Server stopped" << std::endl;
+}
+
+std::string EventIOServer::syncResolve(const std::string& hostname) {
+ struct hostent *he;
+ struct in_addr **addr_list;
+ int i;
+
+ if ( (he = gethostbyname( hostname.c_str() ) ) != NULL) {
+ addr_list = (struct in_addr **) he->h_addr_list;
+ for(i = 0; addr_list[i] != NULL; i++) {
+ return std::string(inet_ntoa(*addr_list[i]));
+ }
+ }
+ return "";
+}
+
+void EventIOServer::determineAddress() {
+
+ char hostname[1024];
+ gethostname(hostname, 1024);
+ _address = std::string(hostname);
+
+#if 0
+ struct sockaddr_storage ss;
+ evutil_socket_t fd;
+ ev_socklen_t socklen = sizeof(ss);
+ char addrbuf[128];
+
+ void *inaddr;
+ const char *addr;
+ int got_port = -1;
+ fd = evhttp_bound_socket_get_fd(_handle);
+ memset(&ss, 0, sizeof(ss));
+ if (getsockname(fd, (struct sockaddr *)&ss, &socklen)) {
+ perror("getsockname() failed");
+ return;
+ }
+
+ if (ss.ss_family == AF_INET) {
+ got_port = ntohs(((struct sockaddr_in*)&ss)->sin_port);
+ inaddr = &((struct sockaddr_in*)&ss)->sin_addr;
+ } else if (ss.ss_family == AF_INET6) {
+ got_port = ntohs(((struct sockaddr_in6*)&ss)->sin6_port);
+ inaddr = &((struct sockaddr_in6*)&ss)->sin6_addr;
+ } else {
+ fprintf(stderr, "Weird address family %d\n",
+ ss.ss_family);
+ return;
+ }
+ addr = evutil_inet_ntop(ss.ss_family, inaddr, addrbuf,
+ sizeof(addrbuf));
+ if (addr) {
+ _address = std::string(addr);
+ } else {
+ fprintf(stderr, "evutil_inet_ntop failed\n");
+ return;
+ }
+#endif
+}
+
+}
+}
+} \ No newline at end of file
diff --git a/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h b/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h
new file mode 100644
index 0000000..7e8eaa9
--- /dev/null
+++ b/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h
@@ -0,0 +1,96 @@
+#ifndef EVENTIOPROCESSOR_H_2CUY93KU
+#define EVENTIOPROCESSOR_H_2CUY93KU
+
+#include "uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h"
+#include "uscxml/Interpreter.h"
+#include "uscxml/Factory.h"
+#include <sys/time.h>
+
+#include <event2/http.h>
+#include <event2/http_struct.h>
+
+namespace uscxml {
+namespace io {
+namespace libevent {
+
+class EventIOServer;
+
+class EventIOProcessor : public uscxml::IOProcessor {
+public:
+ struct SendData {
+ EventIOProcessor* ioProcessor;
+ uscxml::SendRequest req;
+ };
+
+ EventIOProcessor();
+ virtual ~EventIOProcessor();
+ virtual IOProcessor* create(uscxml::Interpreter* interpreter);
+
+ virtual void send(uscxml::SendRequest& req);
+ virtual void invoke(uscxml::InvokeRequest& req);
+ virtual void cancel(const std::string sendId);
+
+ std::string getURL() { return _url; }
+ void setURL(const std::string& url) { _url = url; }
+
+ void start();
+ static void run(void* instance);
+
+ static void httpMakeSendReq(void* userdata, std::string eventName);
+ static void httpSendReqDone(struct evhttp_request *req, void *cb_arg);
+ static void httpRecvReq(struct evhttp_request *req, void *arg);
+
+protected:
+ std::map<std::string, SendData> _sendData;
+
+ std::string _url;
+
+ uscxml::DelayedEventQueue _eventQueue;
+ uscxml::Interpreter* _interpreter;
+ std::map<std::string, struct evhttp_connection*> _httpConnections;
+ std::map<std::string, struct evhttp_request*> _httpRequests;
+ struct evdns_base* _dns;
+
+ friend class EventIOServer;
+};
+
+class EventIOServer {
+private:
+ static EventIOServer* getInstance();
+ EventIOServer(unsigned short port);
+ ~EventIOServer();
+
+ void start();
+ void stop();
+ static void run(void* instance);
+
+ void determineAddress();
+ static std::string syncResolve(const std::string& hostname);
+
+ static void registerProcessor(EventIOProcessor* processor);
+ static void unregisterProcessor(EventIOProcessor* processor);
+
+
+ std::map<std::string, EventIOProcessor*> _processors;
+
+ struct event_base* _base;
+ struct evhttp* _http;
+ struct evhttp_bound_socket* _handle;
+
+ unsigned short _port;
+ std::string _address;
+
+ static EventIOServer* _instance;
+ static tthread::recursive_mutex _instanceMutex;
+ tthread::thread* _thread;
+ tthread::recursive_mutex _mutex;
+ bool _isRunning;
+
+ friend class EventIOProcessor;
+};
+
+}
+}
+}
+
+#endif /* end of include guard: EVENTIOPROCESSOR_H_2CUY93KU */ \ No newline at end of file
diff --git a/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.cpp b/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.cpp
new file mode 100644
index 0000000..a62fefc
--- /dev/null
+++ b/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.cpp
@@ -0,0 +1,3 @@
+#include "uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.h"
+#include "uscxml/Message.h"
+
diff --git a/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.h b/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.h
new file mode 100644
index 0000000..bb7a0fc
--- /dev/null
+++ b/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.h
@@ -0,0 +1,15 @@
+#ifndef MONGOOSEIOPROCESSOR_H_JS0GMSFO
+#define MONGOOSEIOPROCESSOR_H_JS0GMSFO
+
+#include "uscxml/Interpreter.h"
+#include "uscxml/Factory.h"
+
+namespace uscxml {
+
+class MongooseIOProcessor : public IOProcessor {
+
+};
+
+}
+
+#endif /* end of include guard: MONGOOSEIOPROCESSOR_H_JS0GMSFO */
diff --git a/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.cpp b/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.cpp
new file mode 100644
index 0000000..7aa9169
--- /dev/null
+++ b/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.cpp
@@ -0,0 +1,74 @@
+#include "uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h"
+#include "uscxml/Message.h"
+
+#include <pion/http/request.hpp>
+#include <pion/http/message.hpp>
+
+namespace uscxml {
+
+using namespace pion;
+
+PionIOProcessor::PionIOProcessor() {
+}
+
+PionIOProcessor::~PionIOProcessor() {
+
+}
+
+IOProcessor* PionIOProcessor::create(Interpreter* interpreter) {
+ PionIOProcessor* io = new PionIOProcessor();
+ io->_interpreter = interpreter;
+ io->_ioServer = PionIOServer::getInstance();
+ return io;
+}
+
+void handle_connection(pion::tcp::connection_ptr& tcp_conn) {
+}
+
+void PionIOProcessor::send(SendRequest& req) {
+
+ boost::system::error_code error_code;
+ boost::asio::io_service io_service;
+
+ pion::tcp::connection tcp_conn(io_service, 0);
+ error_code = tcp_conn.connect("localhost", 8080);
+ if (error_code) throw error_code; // connection failed
+
+
+
+ http::request httpReq;
+ httpReq.set_method("POST");
+ if (req.event.size() > 0)
+ httpReq.add_header("_scxmleventname", req.event);
+
+ httpReq.send(tcp_conn, error_code);
+
+// http::request_writer writer;
+// writer.
+
+}
+void PionIOProcessor::invoke(InvokeRequest& req) {
+
+}
+void PionIOProcessor::cancel(const std::string sendId) {
+
+}
+
+PionIOServer::PionIOServer() : pion::tcp::server(0) {
+}
+
+PionIOServer::~PionIOServer() {
+}
+
+void PionIOServer::handle_connection(pion::tcp::connection_ptr& tcp_conn) {
+}
+
+PionIOServer* PionIOServer::_instance = NULL;
+PionIOServer* PionIOServer::getInstance() {
+ if (_instance == NULL) {
+ _instance = new PionIOServer();
+ }
+ return _instance;
+}
+
+} \ No newline at end of file
diff --git a/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h b/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h
new file mode 100644
index 0000000..154acdb
--- /dev/null
+++ b/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h
@@ -0,0 +1,47 @@
+#ifndef PIONIOPROCESSOR_H_VAITDNCN
+#define PIONIOPROCESSOR_H_VAITDNCN
+
+#include "uscxml/Interpreter.h"
+#include "uscxml/Factory.h"
+#include "uscxml/concurrency/DelayedEventQueue.h"
+
+#include <pion/http/request_writer.hpp>
+#include <pion/http/request_writer.hpp>
+#include <pion/tcp/server.hpp>
+
+namespace uscxml {
+
+class PionIOServer : public pion::tcp::server {
+public:
+ PionIOServer();
+ virtual ~PionIOServer();
+ DelayedEventQueue _eventQueue;
+
+ virtual void handle_connection(pion::tcp::connection_ptr& tcp_conn);
+
+ static PionIOServer* getInstance();
+ static PionIOServer* _instance;
+
+ pion::http::request_writer_ptr _writer;
+ pion::tcp::connection_ptr _conn;
+
+};
+
+class PionIOProcessor : public IOProcessor {
+public:
+ PionIOProcessor();
+ virtual ~PionIOProcessor();
+ virtual IOProcessor* create(Interpreter* interpreter);
+
+ virtual void send(SendRequest& req);
+ virtual void invoke(InvokeRequest& req);
+ virtual void cancel(const std::string sendId);
+
+protected:
+ Interpreter* _interpreter;
+ PionIOServer* _ioServer;
+};
+
+}
+
+#endif /* end of include guard: PIONIOPROCESSOR_H_VAITDNCN */