From 0ae6c27d9322208053033d9b19c0ffffed3d99eb Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Fri, 5 Oct 2012 17:31:26 +0200 Subject: Implemented DOM --- CMakeLists.txt | 24 ++ contrib/cmake/FindMiles.cmake | 116 ++++++ contrib/cmake/FindUMUNDO.cmake | 112 ++++++ src/uscxml/Factory.cpp | 9 +- src/uscxml/Interpreter.cpp | 113 ++++-- src/uscxml/Interpreter.h | 39 +- src/uscxml/Message.cpp | 4 +- src/uscxml/Message.h | 2 +- src/uscxml/Utilities.cpp | 402 +++++++++++++++++++++ src/uscxml/Utilities.h | 52 +++ src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp | 5 + src/uscxml/datamodel/ecmascript/v8/V8DataModel.h | 7 +- .../datamodel/ecmascript/v8/dom/V8SCXMLDOM.cpp | 188 ++++++++++ .../datamodel/ecmascript/v8/dom/V8SCXMLDOM.h | 58 +++ src/uscxml/debug/SCXMLDotWriter.cpp | 358 ++++++++++++++++++ src/uscxml/debug/SCXMLDotWriter.h | 45 +++ src/uscxml/invoker/modality/MMIComponent.cpp | 42 +++ src/uscxml/invoker/modality/MMIComponent.h | 109 ++++++ src/uscxml/invoker/modality/UmundoComponent.cpp | 49 +++ src/uscxml/invoker/modality/UmundoComponent.h | 30 ++ src/uscxml/invoker/modality/miles/SpatialAudio.cpp | 153 ++++++++ src/uscxml/invoker/modality/miles/SpatialAudio.h | 49 +++ .../basichttp/libevent/EventIOProcessor.cpp | 2 +- test/src/audio/click.wav | Bin 0 -> 5058 bytes test/src/scxml-gui-test.scxml | 13 + test/src/test-completion.cpp | 4 + test/src/test-dom.scxml | 20 + test/src/test-spatial-audio.scxml | 68 ++++ 28 files changed, 2012 insertions(+), 61 deletions(-) create mode 100644 contrib/cmake/FindMiles.cmake create mode 100644 contrib/cmake/FindUMUNDO.cmake create mode 100644 src/uscxml/Utilities.cpp create mode 100644 src/uscxml/Utilities.h create mode 100644 src/uscxml/datamodel/ecmascript/v8/dom/V8SCXMLDOM.cpp create mode 100644 src/uscxml/datamodel/ecmascript/v8/dom/V8SCXMLDOM.h create mode 100644 src/uscxml/debug/SCXMLDotWriter.cpp create mode 100644 src/uscxml/debug/SCXMLDotWriter.h create mode 100644 src/uscxml/invoker/modality/MMIComponent.cpp create mode 100644 src/uscxml/invoker/modality/MMIComponent.h create mode 100644 src/uscxml/invoker/modality/UmundoComponent.cpp create mode 100644 src/uscxml/invoker/modality/UmundoComponent.h create mode 100644 src/uscxml/invoker/modality/miles/SpatialAudio.cpp create mode 100644 src/uscxml/invoker/modality/miles/SpatialAudio.h create mode 100644 test/src/audio/click.wav create mode 100644 test/src/scxml-gui-test.scxml create mode 100644 test/src/test-dom.scxml create mode 100644 test/src/test-spatial-audio.scxml diff --git a/CMakeLists.txt b/CMakeLists.txt index fe9d754..e737a55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,10 +60,34 @@ file(GLOB_RECURSE USCXML_DATAMODEL_V8 list (APPEND USCXML_FILES ${USCXML_DATAMODEL_V8}) list (APPEND USCXML_LIBS ${V8_LIBRARY}) +# curl +find_package(CURL REQUIRED) +include_directories(${CURL_INCLUDE_DIRS}) +list (APPEND USCXML_LIBS ${CURL_LIBRARIES}) + +# umundo +find_package(UMUNDO REQUIRED) +include_directories(${UMUNDO_INCLUDE_DIR}) +list (APPEND USCXML_LIBS ${UMUNDO_LIBRARIES}) + +# miles +find_package(MILES COMPONENTS core audio debug REQUIRED) +include_directories(${MILES_INCLUDE_DIR}) +list (APPEND USCXML_LIBS ${MILES_LIBRARIES}) + +# openal +find_package(OpenAL REQUIRED) +include_directories(${OPENAL_INCLUDE_DIR}) +list(APPEND USCXML_LIBS ${OPENAL_LIBRARY}) + + # the invokers for external services file(GLOB_RECURSE USCXML_INVOKER src/uscxml/invoker/*.cpp src/uscxml/invoker/*.h) list (APPEND USCXML_FILES ${USCXML_INVOKER}) +# debug +file(GLOB_RECURSE USCXML_DEBUG src/uscxml/debug/*.cpp src/uscxml/debug/*.h) +list (APPEND USCXML_FILES ${USCXML_DEBUG}) file(GLOB USCXML_CONCURRENCY src/uscxml/concurrency/*.cpp src/uscxml/concurrency/*.h) list (APPEND USCXML_FILES ${USCXML_CONCURRENCY}) diff --git a/contrib/cmake/FindMiles.cmake b/contrib/cmake/FindMiles.cmake new file mode 100644 index 0000000..3166050 --- /dev/null +++ b/contrib/cmake/FindMiles.cmake @@ -0,0 +1,116 @@ +# - Find Miles +# This module checks if Miles is installed and determines where the +# include files and libraries are. This code sets the following +# variables: +# +# MILES_INCLUDE_DIR = The full path to the miles headers +# MILES_LIBRARIES = All miles libraries for release and debug builds +# +# Example: +# find_package(MILES REQUIRED audio network) +# include_directories(${MILES_INCLUDE_DIR}) +# + +# is this a 64Bit host? +# if (NOT APPLE) +# if(CMAKE_SIZEOF_VOID_P EQUAL 8) +# set(_MILES_64BIT_LIB_POSTFIX 64) +# else() +# set(_MILES_64BIT_LIB_POSTFIX "") +# endif() +# endif() + +################################################### +# where to search for miles headers and libraries +################################################### +set(_MILES_LIB_SEARCHPATH + "/usr/local" + "/opt/local" + "C:/Program Files (x86)/miles" + "C:/Program Files/miles" +) + +################################################### +# get a list of components the user requested +################################################### +set(_MILES_COMPONENTS_TO_PROCESS) +foreach(_MILES_COMPONENT ${MILES_FIND_COMPONENTS}) + STRING(TOUPPER ${_MILES_COMPONENT} _MILES_COMPONENT_UC) + list(APPEND _MILES_COMPONENTS_TO_PROCESS ${_MILES_COMPONENT_UC}) +endforeach() +list(APPEND _MILES_COMPONENTS_TO_PROCESS "CORE") +list(REMOVE_DUPLICATES _MILES_COMPONENTS_TO_PROCESS) + +################################################### +# find the miles header files +################################################### +FIND_PATH(MILES_INCLUDE_DIR miles/miles.h + PATH_SUFFIXES include + PATHS ${_MILES_LIB_SEARCHPATH} + ENV MILES_INCLUDE_DIR +) + +################################################### +# iterate components and try to find libraries +# in debug and release configuration. For release +# we prefer MinSizeRel, for debug we prefer +# RelWithDebInfo. +################################################### +SET(MILES_LIBRARIES) +foreach (_MILES_COMPONENT ${_MILES_COMPONENTS_TO_PROCESS}) + SET(_CURR_COMPONENT "MILES_${_MILES_COMPONENT}_LIBRARY") + STRING(TOLOWER ${_MILES_COMPONENT}${_MILES_64BIT_LIB_POSTFIX} _MILES_COMPONENT_LC) + + # prefer MinSizeRel libraries + FIND_LIBRARY(${_CURR_COMPONENT} + PATH_SUFFIXES lib + NAMES miles_${_MILES_COMPONENT_LC}_s + PATHS ${_MILES_LIB_SEARCHPATH} + ENV MILES_LIB_DIR + ) + if (${_CURR_COMPONENT}) + list(APPEND MILES_LIBRARIES optimized ${${_CURR_COMPONENT}}) + else() + # if no minsize libraries were found try normal release + FIND_LIBRARY(${_CURR_COMPONENT} + PATH_SUFFIXES lib + NAMES miles_${_MILES_COMPONENT_LC} + PATHS ${_MILES_LIB_SEARCHPATH} + ENV MILES_LIB_DIR + ) + if (${_CURR_COMPONENT}) + list(APPEND MILES_LIBRARIES optimized ${${_CURR_COMPONENT}}) + endif() + endif() + + # prefer RelWithDebInfo libraries + FIND_LIBRARY(${_CURR_COMPONENT}_DEBUG + PATH_SUFFIXES lib + NAMES miles_${_MILES_COMPONENT_LC}_rd + PATHS ${_MILES_LIB_SEARCHPATH} + ENV MILES_LIB_DIR + ) + if (${_CURR_COMPONENT}_DEBUG) + list(APPEND MILES_LIBRARIES debug ${${_CURR_COMPONENT}_DEBUG}) + else() + message("Searching miles_${_MILES_COMPONENT_LC}_d") + FIND_LIBRARY(${_CURR_COMPONENT}_DEBUG + PATH_SUFFIXES lib + NAMES miles_${_MILES_COMPONENT_LC}_d + PATHS ${_MILES_LIB_SEARCHPATH} + ENV MILES_LIB_DIR + ) + if (${_CURR_COMPONENT}_DEBUG) + list(APPEND MILES_LIBRARIES debug ${${_CURR_COMPONENT}_DEBUG}) + endif() + endif() + + if (NOT ${_CURR_COMPONENT} AND NOT ${_CURR_COMPONENT}_DEBUG) + message(FATAL_ERROR "Could not find miles component ${_MILES_COMPONENT}") + endif() +endforeach() + + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(MILES DEFAULT_MSG MILES_LIBRARIES MILES_INCLUDE_DIR) +MARK_AS_ADVANCED(MILES_INCLUDE_DIR MILES_LIBRARIES) diff --git a/contrib/cmake/FindUMUNDO.cmake b/contrib/cmake/FindUMUNDO.cmake new file mode 100644 index 0000000..06df54a --- /dev/null +++ b/contrib/cmake/FindUMUNDO.cmake @@ -0,0 +1,112 @@ +# - Find UMUNDO +# This module checks if UMundo is installed and determines where the +# include files and libraries are. This code sets the following +# variables: +# +# UMUNDO_INCLUDE_DIR = The full path to the umundo headers +# UMUNDO_LIBRARIES = All umundo libraries for release and debug builds +# +# Example: +# find_package(UMUNDO REQUIRED util serial rpc) +# include_directories(${UMUNDO_INCLUDE_DIR}) +# + +# is this a 64Bit host? +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(64BIT_LIB_POSTFIX 64) +else() + set(64BIT_LIB_POSTFIX "") +endif() + +################################################### +# where to search for umundo headers and libraries +################################################### +set(_UMUNDO_LIB_SEARCHPATH + "/usr/local" + "/opt/local" + "C:/Program Files (x86)/uMundo" +) + +################################################### +# get a list of components the user requested +################################################### +set(_UMUNDO_COMPONENTS_TO_PROCESS) +foreach(_UMUNDO_COMPONENT ${UMUNDO_FIND_COMPONENTS}) + STRING(TOUPPER ${_UMUNDO_COMPONENT} _UMUNDO_COMPONENT_UC) + list(APPEND _UMUNDO_COMPONENTS_TO_PROCESS ${_UMUNDO_COMPONENT_UC}) +endforeach() +list(APPEND _UMUNDO_COMPONENTS_TO_PROCESS "CORE") +list(REMOVE_DUPLICATES _UMUNDO_COMPONENTS_TO_PROCESS) + +################################################### +# find the umundo header files +################################################### +FIND_PATH(UMUNDO_INCLUDE_DIR umundo/core.h + PATH_SUFFIXES include + PATHS ${_UMUNDO_LIB_SEARCHPATH} + ENV UMUNDO_INCLUDE_DIR +) + +################################################### +# iterate components and try to find libraries +# in debug and release configuration. For release +# we prefer MinSizeRel, for debug we prefer +# RelWithDebInfo. +################################################### +SET(UMUNDO_LIBRARIES) +foreach (_UMUNDO_COMPONENT ${_UMUNDO_COMPONENTS_TO_PROCESS}) + SET(_CURR_COMPONENT "UMUNDO_${_UMUNDO_COMPONENT}_LIBRARY") + STRING(TOLOWER ${_UMUNDO_COMPONENT}${64BIT_LIB_POSTFIX} _UMUNDO_COMPONENT_LC) + + # prefer MinSizeRel libraries + FIND_LIBRARY(${_CURR_COMPONENT} + PATH_SUFFIXES lib + NAMES umundo${_UMUNDO_COMPONENT_LC}_s + PATHS ${_UMUNDO_LIB_SEARCHPATH} + ENV UMUNDO_LIB_DIR + ) + if (${_CURR_COMPONENT}) + list(APPEND UMUNDO_LIBRARIES optimized ${${_CURR_COMPONENT}}) + else() + # if no minsize libraries were found try normal release + FIND_LIBRARY(${_CURR_COMPONENT} + PATH_SUFFIXES lib + NAMES umundo${_UMUNDO_COMPONENT_LC} + PATHS ${_UMUNDO_LIB_SEARCHPATH} + ENV UMUNDO_LIB_DIR + ) + if (${_CURR_COMPONENT}) + list(APPEND UMUNDO_LIBRARIES optimized ${${_CURR_COMPONENT}}) + endif() + endif() + + # prefer RelWithDebInfo libraries + FIND_LIBRARY(${_CURR_COMPONENT}_DEBUG + PATH_SUFFIXES lib + NAMES umundo${_UMUNDO_COMPONENT_LC}_rd + PATHS ${_UMUNDO_LIB_SEARCHPATH} + ENV UMUNDO_LIB_DIR + ) + if (${_CURR_COMPONENT}_DEBUG) + list(APPEND UMUNDO_LIBRARIES debug ${${_CURR_COMPONENT}_DEBUG}) + else() + FIND_LIBRARY(${_CURR_COMPONENT}_DEBUG + PATH_SUFFIXES lib + NAMES umundo${_UMUNDO_COMPONENT_LC}_d + PATHS ${_UMUNDO_LIB_SEARCHPATH} + ENV UMUNDO_LIB_DIR + ) + if (${_CURR_COMPONENT}_DEBUG) + list(APPEND UMUNDO_LIBRARIES debug ${${_CURR_COMPONENT}_DEBUG}) + endif() + endif() + + if (NOT ${_CURR_COMPONENT} AND NOT ${_CURR_COMPONENT}_DEBUG) + message(FATAL_ERROR "Could not find umundo component ${_UMUNDO_COMPONENT}") + endif() +endforeach() + + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(UMUNDO DEFAULT_MSG UMUNDO_LIBRARIES UMUNDO_INCLUDE_DIR) +MARK_AS_ADVANCED(UMUNDO_INCLUDE_DIR UMUNDO_LIBRARIES) diff --git a/src/uscxml/Factory.cpp b/src/uscxml/Factory.cpp index b2346c2..36e0523 100644 --- a/src/uscxml/Factory.cpp +++ b/src/uscxml/Factory.cpp @@ -3,17 +3,24 @@ //#include "uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h" #include "uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h" #include "uscxml/invoker/scxml/USCXMLInvoker.h" +#include "uscxml/invoker/modality/miles/SpatialAudio.h" namespace uscxml { Factory::Factory() { _dataModels["ecmascript"] = new V8DataModel(); // _ioProcessors["basichttp"] = new PionIOProcessor(); - _ioProcessors["basichttp"] = new EventIOProcessor(); + // use basichttp for transporting to/from scxml sessions as well + _ioProcessors["basichttp"] = new EventIOProcessor(); _ioProcessors["http://www.w3.org/TR/scxml/#SCXMLEventProcessor"] = _ioProcessors["basichttp"]; + _invoker["scxml"] = new USCXMLInvoker(); _invoker["http://www.w3.org/TR/scxml/"] = _invoker["scxml"]; + + _invoker["spatial-audio"] = new SpatialAudio(); + _invoker["http://www.smartvortex.eu/mmi/spatial-audio/"] = _invoker["spatial-audio"]; + } void Factory::registerIOProcessor(const std::string type, IOProcessor* ioProcessor) { diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index f965e1e..fbc048f 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -1,4 +1,5 @@ #include "uscxml/Interpreter.h" +#include "uscxml/debug/SCXMLDotWriter.h" #include @@ -127,10 +128,11 @@ void Interpreter::init() { NodeList scxmls = _doc.getElementsByTagName("scxml"); if (scxmls.getLength() > 0) { _scxml = (Arabica::DOM::Element)scxmls.item(0); + normalize(_doc); + _name = (HAS_ATTR(_scxml, "name") ? ATTR(_scxml, "name") : getUUID()); } else { LOG(ERROR) << "Cannot find SCXML element" << std::endl; } - _name = (HAS_ATTR(_scxml, "name") ? ATTR(_scxml, "name") : getUUID()); } } @@ -170,7 +172,6 @@ void Interpreter::interpret() { // dump(); _sessionId = getUUID(); - normalize(_doc); if(HAS_ATTR(_scxml, "datamodel")) { _dataModel = Factory::getDataModel(ATTR(_scxml, "datamodel"), this); @@ -208,21 +209,10 @@ void Interpreter::interpret() { } } - // executeTransitionContent - Arabica::DOM::Element initialState = (Arabica::DOM::Element)getInitialState(); - - // create a pseudo initial and transition element - NodeSet initialTransitions; - Arabica::DOM::Element initialElem = _doc.createElement("initial"); - Arabica::DOM::Element 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); + // we made sure during normalization that this element exists + NodeSet initialTransitions = _xpath.evaluate("/" + _nsPrefix + "scxml/" + _nsPrefix + "initial/" + _nsPrefix + "transition", _doc).asNodeSet(); + assert(initialTransitions.size() > 0); + initialTransitions.push_back(initialTransitions[0]); enterStates(initialTransitions); mainEventLoop(); @@ -253,7 +243,7 @@ void Interpreter::initializeData(const Arabica::DOM::Node& data) { NodeList dataChilds = data.getChildNodes(); for (int i = 0; i < dataChilds.getLength(); i++) { if (dataChilds.item(i).getNodeType() == Node_base::TEXT_NODE) { - Data value = Data(dataChilds.item(i), Data::INTERPRETED); + Data value = Data(dataChilds.item(i).getNodeValue()); _dataModel->assign(ATTR(data, "id"), value); break; } @@ -266,7 +256,7 @@ void Interpreter::initializeData(const Arabica::DOM::Node& data) { } } -void Interpreter::normalize(const Arabica::DOM::Node& node) { +void Interpreter::normalize(const Arabica::DOM::Document& node) { // make sure every state has an id and set isFirstEntry to true Arabica::XPath::NodeSet states = _xpath.evaluate("//" + _nsPrefix + "state", _doc).asNodeSet(); for (int i = 0; i < states.size(); i++) { @@ -313,6 +303,15 @@ void Interpreter::normalize(const Arabica::DOM::Node& node) { if (!((Arabica::DOM::Element)scxml[0]).hasAttribute("id")) { ((Arabica::DOM::Element)scxml[0]).setAttribute("id", getUUID()); } + + // create a pseudo initial and transition element + Arabica::DOM::Element initialState = (Arabica::DOM::Element)getInitialState(); + Arabica::DOM::Element initialElem = _doc.createElement("initial"); + Arabica::DOM::Element transitionElem = _doc.createElement("transition"); + transitionElem.setAttribute("target", initialState.getAttribute("id")); + initialElem.appendChild(transitionElem); + _scxml.appendChild(initialElem); + } void Interpreter::mainEventLoop() { @@ -323,7 +322,7 @@ void Interpreter::mainEventLoop() { // Here we handle eventless transitions and transitions // triggered by internal events until machine is stable while(_running && !_stable) { -#if 1 +#if 0 std::cout << "Configuration: "; for (int i = 0; i < _configuration.size(); i++) { std::cout << ((Arabica::DOM::Element)_configuration[i]).getAttribute("id") << ", "; @@ -337,10 +336,11 @@ void Interpreter::mainEventLoop() { } else { Event internalEvent = _internalQueue.front(); _internalQueue.pop_front(); -#if 1 +#if 0 std::cout << "Received internal event " << internalEvent.name << std::endl; #endif - _dataModel->setEvent(internalEvent); + if (_dataModel) + _dataModel->setEvent(internalEvent); enabledTransitions = selectTransitions(internalEvent.name); } } @@ -553,8 +553,7 @@ void Interpreter::send(const Arabica::DOM::Node& element) { } std::string paramValue; if (HAS_ATTR(params[i], "expr") && _dataModel) { - std::string location = _dataModel->evalAsString(ATTR(params[i], "expr")); - paramValue = _dataModel->evalAsString(location); + paramValue = _dataModel->evalAsString(ATTR(params[i], "expr")); } else if(HAS_ATTR(params[i], "location") && _dataModel) { paramValue = _dataModel->evalAsString(ATTR(params[i], "location")); } else { @@ -597,12 +596,23 @@ void Interpreter::delayedSend(void* userdata, std::string eventName) { Interpreter* THIS = data->first; SendRequest sendReq = data->second; - if (boost::iequals(sendReq.target, "_parent")) { + if (boost::iequals(sendReq.target, "#_parent")) { + // send to parent scxml session if (THIS->_invoker != NULL) { THIS->_invoker->sendToParent(sendReq); } else { LOG(ERROR) << "Can not send to parent, we were not invoked" << std::endl; } + } else if (sendReq.target.find_first_of("#_") == 0) { + // send to invoker + std::string invokeId = sendReq.target.substr(2, sendReq.target.length() - 2); + if (THIS->_invokerIds.find(invokeId) != THIS->_invokerIds.end()) { + THIS->_invokerIds[invokeId]->send(sendReq); + } else { + LOG(ERROR) << "Can not send to invoked component " << invokeId << ", no such invokeId" << std::endl; + } + } else if (sendReq.target.length() == 0) { + THIS->receive(sendReq); } else { IOProcessor* ioProc = THIS->getIOProcessor(sendReq.type); if (ioProc != NULL) { @@ -674,13 +684,17 @@ void Interpreter::invoke(const Arabica::DOM::Node& element) { // params NodeSet params = _xpath.evaluate("" + _nsPrefix + "param", element).asNodeSet(); for (int i = 0; i < params.size(); i++) { - if (HAS_ATTR(params[i], "name")) { + 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")); + if (HAS_ATTR(params[i], "expr")) { + if (_dataModel) { + paramValue = _dataModel->evalAsString(ATTR(params[i], "expr")); + } else { + paramValue = ATTR(params[i], "expr"); + } } else if(HAS_ATTR(params[i], "location") && _dataModel) { paramValue = _dataModel->evalAsString(ATTR(params[i], "location")); } else { @@ -859,7 +873,7 @@ bool Interpreter::isPreemptingTransition(const Arabica::DOM::Node& } void Interpreter::microstep(const Arabica::XPath::NodeSet& enabledTransitions) { -#if 1 +#if 0 std::cout << "Transitions: "; for (int i = 0; i < enabledTransitions.size(); i++) { std::cout << ((Arabica::DOM::Element)getSourceState(enabledTransitions[i])).getAttribute("id") << " -> " << std::endl; @@ -1374,14 +1388,13 @@ void Interpreter::addStatesToEnter(const Arabica::DOM::Node& state, Arabica::XPath::NodeSet Interpreter::getChildStates(const Arabica::DOM::Node& state) { Arabica::XPath::NodeSet childs; - Arabica::XPath::NodeSet stateChilds = _xpath.evaluate("" + _nsPrefix + "state", state).asNodeSet(); - Arabica::XPath::NodeSet parallelChilds = _xpath.evaluate("" + _nsPrefix + "parallel", state).asNodeSet(); - Arabica::XPath::NodeSet finalChilds = _xpath.evaluate("" + _nsPrefix + "final", state).asNodeSet(); - childs.insert(childs.begin(), stateChilds.begin(), stateChilds.end()); - childs.insert(childs.begin(), parallelChilds.begin(), parallelChilds.end()); - childs.insert(childs.begin(), finalChilds.begin(), finalChilds.end()); - + Arabica::DOM::NodeList childElems = state.getChildNodes(); + for (int i = 0; i < childElems.getLength(); i++) { + if (isState(childElems.item(i))) { + childs.push_back(childElems.item(i)); + } + } return childs; } @@ -1412,7 +1425,7 @@ Arabica::DOM::Node Interpreter::findLCCA(const Arabica::XPath::Node Arabica::DOM::Node Interpreter::getState(const std::string& stateId) { // first try atomic and compund states - std::cout << _nsPrefix << stateId << std::endl; +// std::cout << _nsPrefix << stateId << std::endl; NodeSet target = _xpath.evaluate("//" + _nsPrefix + "state[@id='" + stateId + "']", _doc).asNodeSet(); if (target.size() > 0) goto FOUND; @@ -1481,6 +1494,18 @@ Arabica::DOM::Node Interpreter::getInitialState(Arabica::DOM::Node< NodeSet Interpreter::getTargetStates(const Arabica::DOM::Node& transition) { NodeSet targetStates; + + // if we are called with a state, process all its transitions + if (isState(transition)) { + NodeList childs = transition.getChildNodes(); + for (int i = 0; i < childs.getLength(); i++) { + if (childs.item(i).getNodeType() == Node_base::ELEMENT_NODE && boost::iequals(TAGNAME(childs.item(i)), "transition")) { + targetStates.push_back(getTargetStates(childs.item(i))); + } + } + return targetStates; + } + std::string targetId = ((Arabica::DOM::Element)transition).getAttribute("target"); std::vector targetIds = Interpreter::tokenizeIdRefs(ATTR(transition, "target")); @@ -1581,6 +1606,20 @@ bool Interpreter::isFinal(const Arabica::DOM::Node& state) { return false; } +bool Interpreter::isInitial(const Arabica::DOM::Node& state) { + if (!isState(state)) + return false; + + Arabica::DOM::Node parent = state.getParentNode(); + if (!isState(parent)) + return true; // scxml element + + if (getInitialState(parent) == state) + return true; // every nested node + + return false; +} + bool Interpreter::isPseudoState(const Arabica::DOM::Node& state) { std::string tagName = TAGNAME(state); if (boost::iequals("initial", tagName)) diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h index 4a948dc..964320d 100644 --- a/src/uscxml/Interpreter.h +++ b/src/uscxml/Interpreter.h @@ -48,6 +48,8 @@ namespace uscxml { DataModel* getDataModel() { return _dataModel; } Invoker* getInvoker() { return _invoker; } void setInvoker(Invoker* invoker) { _invoker = invoker; } + std::string getNSPrefix() { return _nsPrefix; } + Arabica::XPath::XPath& getXPath() { return _xpath; } void waitForStabilization(); @@ -55,7 +57,8 @@ namespace uscxml { void receiveInternal(Event& event) { _internalQueue.push_back(event); } Arabica::XPath::NodeSet getConfiguration() { return _configuration; } Arabica::DOM::Node getState(const std::string& stateId); - + Arabica::DOM::Document& getDocument() { return _doc; } + const std::string& getName() { return _name; } const std::string& getSessionId() { return _sessionId; } @@ -64,11 +67,27 @@ namespace uscxml { void dump(); static void dump(const Arabica::DOM::Node& node, int lvl = 0); + static bool isState(const Arabica::DOM::Node& state); + static bool isPseudoState(const Arabica::DOM::Node& state); + static bool isTransitionTarget(const Arabica::DOM::Node& elem); + static bool isTargetless(const Arabica::DOM::Node& transition); + static bool isAtomic(const Arabica::DOM::Node& state); + static bool isFinal(const Arabica::DOM::Node& state); + static bool isHistory(const Arabica::DOM::Node& state); + static bool isParallel(const Arabica::DOM::Node& state); + static bool isCompound(const Arabica::DOM::Node& state); + static bool isDescendant(const Arabica::DOM::Node& s1, const Arabica::DOM::Node& s2); + + bool isInitial(const Arabica::DOM::Node& state); + Arabica::DOM::Node getInitialState(Arabica::DOM::Node state = Arabica::DOM::Node()); + static Arabica::XPath::NodeSet getChildStates(const Arabica::DOM::Node& state); + Arabica::XPath::NodeSet getTargetStates(const Arabica::DOM::Node& transition); + protected: Interpreter(); void init(); - void normalize(const Arabica::DOM::Node& node); + void normalize(const Arabica::DOM::Document& node); void setupIOProcessors(); void mainEventLoop(); @@ -116,9 +135,6 @@ namespace uscxml { Arabica::XPath::NodeSet selectEventlessTransitions(); Arabica::XPath::NodeSet selectTransitions(const std::string& event); - Arabica::XPath::NodeSet getTargetStates(const Arabica::DOM::Node& transition); - Arabica::XPath::NodeSet getChildStates(const Arabica::DOM::Node& state); - Arabica::DOM::Node getInitialState(Arabica::DOM::Node state = Arabica::DOM::Node()); Arabica::DOM::Node getSourceState(const Arabica::DOM::Node& transition); Arabica::DOM::Node findLCCA(const Arabica::XPath::NodeSet& states); static Arabica::XPath::NodeSet getProperAncestors(const Arabica::DOM::Node& s1, const Arabica::DOM::Node& s2); @@ -138,18 +154,7 @@ namespace uscxml { bool isInFinalState(const Arabica::DOM::Node& state); bool isWithinSameChild(const Arabica::DOM::Node& transition); bool parentIsScxmlState(Arabica::DOM::Node state); - - static bool isState(const Arabica::DOM::Node& state); - static bool isPseudoState(const Arabica::DOM::Node& state); - static bool isTransitionTarget(const Arabica::DOM::Node& elem); - static bool isTargetless(const Arabica::DOM::Node& transition); - static bool isAtomic(const Arabica::DOM::Node& state); - static bool isFinal(const Arabica::DOM::Node& state); - static bool isHistory(const Arabica::DOM::Node& state); - static bool isParallel(const Arabica::DOM::Node& state); - static bool isCompound(const Arabica::DOM::Node& state); - static bool isDescendant(const Arabica::DOM::Node& s1, const Arabica::DOM::Node& s2); - + static std::vector tokenizeIdRefs(const std::string& idRefs); static boost::uuids::random_generator uuidGen; diff --git a/src/uscxml/Message.cpp b/src/uscxml/Message.cpp index a2990b9..9b713ca 100644 --- a/src/uscxml/Message.cpp +++ b/src/uscxml/Message.cpp @@ -7,7 +7,7 @@ namespace uscxml { static int _dataIndentation = 1; -Data::Data(const Arabica::DOM::Node& dom, Type type) { +Data::Data(const Arabica::DOM::Node& dom) { // we may need to convert some keys to arrays if we have the same name as an element std::map > arrays; // Interpreter::dump(dom); @@ -54,7 +54,7 @@ Data::Data(const Arabica::DOM::Node& dom, Type type) { } } else { atom = dom.getNodeValue(); - this->type = type; + type = VERBATIM; } std::map >::iterator arrayIter = arrays.begin(); diff --git a/src/uscxml/Message.h b/src/uscxml/Message.h index be7747d..34b85e1 100644 --- a/src/uscxml/Message.h +++ b/src/uscxml/Message.h @@ -24,7 +24,7 @@ public: Data() {} Data(const std::string& atom_, Type type_ = INTERPRETED) : atom(atom_), type(type_) {} - Data(const Arabica::DOM::Node& dom, Type type = VERBATIM); + Data(const Arabica::DOM::Node& dom); virtual ~Data() {} static Data fromXML(const std::string& xmlString); diff --git a/src/uscxml/Utilities.cpp b/src/uscxml/Utilities.cpp new file mode 100644 index 0000000..04a376c --- /dev/null +++ b/src/uscxml/Utilities.cpp @@ -0,0 +1,402 @@ +/***************************************************************************** + * + * Taken in its entirety from http://curl.haxx.se/libcurl/c/fopen.html + * + * This example source code introduces a c library buffered I/O interface to + * URL reads it supports fopen(), fread(), fgets(), feof(), fclose(), + * rewind(). Supported functions have identical prototypes to their normal c + * lib namesakes and are preceaded by url_ . + * + * Using this code you can replace your program's fopen() with url_fopen() + * and fread() with url_fread() and it become possible to read remote streams + * instead of (only) local files. Local files (ie those that can be directly + * fopened) will drop back to using the underlying clib implementations + * + * See the main() function at the bottom that shows an app that retrives from a + * specified url using fgets() and fread() and saves as two output files. + * + * Copyright (c) 2003 Simtec Electronics + * + * Re-implemented by Vincent Sanders with extensive + * reference to original curl example code + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This example requires libcurl 7.9.7 or later. + */ + +#include +#include +#ifndef WIN32 +#include +#endif +#include +#include + +#include "uscxml/Utilities.h" + +/* we use a global one for convenience */ +CURLM *multi_handle; + +/* curl calls this routine to get more data */ +static size_t write_callback(char *buffer, + size_t size, + size_t nitems, + void *userp) +{ + char *newbuff; + size_t rembuff; + + URL_FILE *url = (URL_FILE *)userp; + size *= nitems; + + rembuff=url->buffer_len - url->buffer_pos; /* remaining space in buffer */ + + if(size > rembuff) { + /* not enough space in buffer */ + newbuff=(char*)realloc(url->buffer,url->buffer_len + (size - rembuff)); + if(newbuff==NULL) { + fprintf(stderr,"callback buffer grow failed\n"); + size=rembuff; + } + else { + /* realloc suceeded increase buffer size*/ + url->buffer_len+=size - rembuff; + url->buffer=newbuff; + } + } + + memcpy(&url->buffer[url->buffer_pos], buffer, size); + url->buffer_pos += size; + + return size; +} + +/* use to attempt to fill the read buffer up to requested number of bytes */ +static int fill_buffer(URL_FILE *file, size_t want) +{ + fd_set fdread; + fd_set fdwrite; + fd_set fdexcep; + struct timeval timeout; + int rc; + + /* only attempt to fill buffer if transactions still running and buffer + * doesnt exceed required size already + */ + if((!file->still_running) || (file->buffer_pos > want)) + return 0; + + /* attempt to fill buffer */ + do { + int maxfd = -1; + long curl_timeo = -1; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + FD_ZERO(&fdexcep); + + /* set a suitable timeout to fail on */ + timeout.tv_sec = 60; /* 1 minute */ + timeout.tv_usec = 0; + + curl_multi_timeout(multi_handle, &curl_timeo); + if(curl_timeo >= 0) { + timeout.tv_sec = curl_timeo / 1000; + if(timeout.tv_sec > 1) + timeout.tv_sec = 1; + else + timeout.tv_usec = (curl_timeo % 1000) * 1000; + } + + /* get file descriptors from the transfers */ + curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd); + + /* In a real-world program you OF COURSE check the return code of the + function calls. On success, the value of maxfd is guaranteed to be + greater or equal than -1. We call select(maxfd + 1, ...), specially + in case of (maxfd == -1), we call select(0, ...), which is basically + equal to sleep. */ + + rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout); + + switch(rc) { + case -1: + /* select error */ + break; + + case 0: + default: + /* timeout or readable/writable sockets */ + curl_multi_perform(multi_handle, &file->still_running); + break; + } + } while(file->still_running && (file->buffer_pos < want)); + return 1; +} + +/* use to remove want bytes from the front of a files buffer */ +static int use_buffer(URL_FILE *file,int want) +{ + /* sort out buffer */ + if((file->buffer_pos - want) <=0) { + /* ditch buffer - write will recreate */ + if(file->buffer) + free(file->buffer); + + file->buffer=NULL; + file->buffer_pos=0; + file->buffer_len=0; + } + else { + /* move rest down make it available for later */ + memmove(file->buffer, + &file->buffer[want], + (file->buffer_pos - want)); + + file->buffer_pos -= want; + } + return 0; +} + +URL_FILE *url_fopen(const char *url,const char *operation) +{ + /* this code could check for URLs or types in the 'url' and + basicly use the real fopen() for standard files */ + + URL_FILE *file; + (void)operation; + + file = (URL_FILE*)malloc(sizeof(URL_FILE)); + if(!file) + return NULL; + + memset(file, 0, sizeof(URL_FILE)); + + if((file->handle.file=fopen(url,operation))) + file->type = CFTYPE_FILE; /* marked as URL */ + + else { + file->type = CFTYPE_CURL; /* marked as URL */ + file->handle.curl = curl_easy_init(); + + curl_easy_setopt(file->handle.curl, CURLOPT_URL, url); + curl_easy_setopt(file->handle.curl, CURLOPT_WRITEDATA, file); + curl_easy_setopt(file->handle.curl, CURLOPT_VERBOSE, 0L); + curl_easy_setopt(file->handle.curl, CURLOPT_WRITEFUNCTION, write_callback); + + if(!multi_handle) + multi_handle = curl_multi_init(); + + curl_multi_add_handle(multi_handle, file->handle.curl); + + /* lets start the fetch */ + curl_multi_perform(multi_handle, &file->still_running); + + if((file->buffer_pos == 0) && (!file->still_running)) { + /* if still_running is 0 now, we should return NULL */ + + /* make sure the easy handle is not in the multi handle anymore */ + curl_multi_remove_handle(multi_handle, file->handle.curl); + + /* cleanup */ + curl_easy_cleanup(file->handle.curl); + + free(file); + + file = NULL; + } + } + return file; +} + +int url_fclose(URL_FILE *file) +{ + int ret=0;/* default is good return */ + + switch(file->type) { + case CFTYPE_FILE: + ret=fclose(file->handle.file); /* passthrough */ + break; + + case CFTYPE_CURL: + /* make sure the easy handle is not in the multi handle anymore */ + curl_multi_remove_handle(multi_handle, file->handle.curl); + + /* cleanup */ + curl_easy_cleanup(file->handle.curl); + break; + + default: /* unknown or supported type - oh dear */ + ret=EOF; + errno=EBADF; + break; + } + + if(file->buffer) + free(file->buffer);/* free any allocated buffer space */ + + free(file); + + return ret; +} + +int url_feof(URL_FILE *file) +{ + int ret=0; + + switch(file->type) { + case CFTYPE_FILE: + ret=feof(file->handle.file); + break; + + case CFTYPE_CURL: + if((file->buffer_pos == 0) && (!file->still_running)) + ret = 1; + break; + + default: /* unknown or supported type - oh dear */ + ret=-1; + errno=EBADF; + break; + } + return ret; +} + +size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file) +{ + size_t want; + + switch(file->type) { + case CFTYPE_FILE: + want=fread(ptr,size,nmemb,file->handle.file); + break; + + case CFTYPE_CURL: + want = nmemb * size; + + fill_buffer(file,want); + + /* check if theres data in the buffer - if not fill_buffer() + * either errored or EOF */ + if(!file->buffer_pos) + return 0; + + /* ensure only available data is considered */ + if(file->buffer_pos < want) + want = file->buffer_pos; + + /* xfer data to caller */ + memcpy(ptr, file->buffer, want); + + use_buffer(file,want); + + want = want / size; /* number of items */ + break; + + default: /* unknown or supported type - oh dear */ + want=0; + errno=EBADF; + break; + + } + return want; +} + +char *url_fgets(char *ptr, size_t size, URL_FILE *file) +{ + size_t want = size - 1;/* always need to leave room for zero termination */ + size_t loop; + + switch(file->type) { + case CFTYPE_FILE: + ptr = fgets(ptr,size,file->handle.file); + break; + + case CFTYPE_CURL: + fill_buffer(file,want); + + /* check if theres data in the buffer - if not fill either errored or + * EOF */ + if(!file->buffer_pos) + return NULL; + + /* ensure only available data is considered */ + if(file->buffer_pos < want) + want = file->buffer_pos; + + /*buffer contains data */ + /* look for newline or eof */ + for(loop=0;loop < want;loop++) { + if(file->buffer[loop] == '\n') { + want=loop+1;/* include newline */ + break; + } + } + + /* xfer data to caller */ + memcpy(ptr, file->buffer, want); + ptr[want]=0;/* allways null terminate */ + + use_buffer(file,want); + + break; + + default: /* unknown or supported type - oh dear */ + ptr=NULL; + errno=EBADF; + break; + } + + return ptr;/*success */ +} + +void url_rewind(URL_FILE *file) +{ + switch(file->type) { + case CFTYPE_FILE: + rewind(file->handle.file); /* passthrough */ + break; + + case CFTYPE_CURL: + /* halt transaction */ + curl_multi_remove_handle(multi_handle, file->handle.curl); + + /* restart */ + curl_multi_add_handle(multi_handle, file->handle.curl); + + /* ditch buffer - write will recreate - resets stream pos*/ + if(file->buffer) + free(file->buffer); + + file->buffer=NULL; + file->buffer_pos=0; + file->buffer_len=0; + + break; + + default: /* unknown or supported type - oh dear */ + break; + } +} + \ No newline at end of file diff --git a/src/uscxml/Utilities.h b/src/uscxml/Utilities.h new file mode 100644 index 0000000..d8fd0f8 --- /dev/null +++ b/src/uscxml/Utilities.h @@ -0,0 +1,52 @@ +#ifndef UTILITIES_H_A89E99LI +#define UTILITIES_H_A89E99LI + +#include +#include +#include + +// see http://stackoverflow.com/questions/228005/alternative-to-itoa-for-converting-integer-to-string-c +template std::string toStr(T tmp) { + std::ostringstream out; + out << tmp; + return out.str(); +} + +template T strTo(std::string tmp) { + T output; + std::istringstream in(tmp); + in >> output; + return output; +} + + +enum fcurl_type_e { + CFTYPE_NONE=0, + CFTYPE_FILE=1, + CFTYPE_CURL=2 +}; + +struct fcurl_data +{ + enum fcurl_type_e type; /* type of handle */ + union { + CURL *curl; + FILE *file; + } handle; /* handle */ + + char *buffer; /* buffer to store cached data*/ + size_t buffer_len; /* currently allocated buffers length */ + size_t buffer_pos; /* end of data in buffer*/ + int still_running; /* Is background url fetch still in progress */ +}; + +typedef struct fcurl_data URL_FILE; + +URL_FILE *url_fopen(const char *url,const char *operation); +int url_fclose(URL_FILE *file); +int url_feof(URL_FILE *file); +size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file); +char * url_fgets(char *ptr, size_t size, URL_FILE *file); +void url_rewind(URL_FILE *file); + +#endif /* end of include guard: UTILITIES_H_A89E99LI */ diff --git a/src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp index 52c7ad7..28ed8c1 100644 --- a/src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp +++ b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp @@ -1,4 +1,5 @@ #include "uscxml/datamodel/ecmascript/v8/V8DataModel.h" +#include "dom/V8SCXMLDOM.h" #include "uscxml/Message.h" namespace uscxml { @@ -19,11 +20,15 @@ DataModel* V8DataModel::create(Interpreter* interpreter) { v8::Handle global = v8::ObjectTemplate::New(); global->Set(v8::String::New("In"), v8::FunctionTemplate::New(jsIn, v8::External::New(reinterpret_cast(dm)))); + + dm->_dom = new V8SCXMLDOM(interpreter); + global->Set(v8::String::New("document"), dm->_dom->getDocument()); dm->_contexts.push_back(v8::Context::New(NULL, global)); dm->setName(interpreter->getName()); dm->setSessionId(interpreter->getSessionId()); dm->eval("_ioprocessors = {};"); + return dm; } diff --git a/src/uscxml/datamodel/ecmascript/v8/V8DataModel.h b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.h index 340b6ee..b8bfa6d 100644 --- a/src/uscxml/datamodel/ecmascript/v8/V8DataModel.h +++ b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.h @@ -6,8 +6,9 @@ #include namespace uscxml { - class Event; - class Data; + class Event; + class Data; + class V8SCXMLDOM; } namespace uscxml { @@ -59,6 +60,8 @@ protected: std::string _sessionId; std::string _name; + V8SCXMLDOM* _dom; + Event _event; v8::Persistent _globalTemplate; v8::Persistent _eventTemplate; diff --git a/src/uscxml/datamodel/ecmascript/v8/dom/V8SCXMLDOM.cpp b/src/uscxml/datamodel/ecmascript/v8/dom/V8SCXMLDOM.cpp new file mode 100644 index 0000000..390d110 --- /dev/null +++ b/src/uscxml/datamodel/ecmascript/v8/dom/V8SCXMLDOM.cpp @@ -0,0 +1,188 @@ +#include "V8SCXMLDOM.h" + +namespace uscxml { + + using namespace Arabica::DOM; + using namespace Arabica::XPath; + + V8SCXMLDOM::V8SCXMLDOM(Interpreter* interpreter) { + _interpreter = interpreter; + } + + v8::Handle V8SCXMLDOM::getDocument() { + v8::Handle documentTmpl = v8::ObjectTemplate::New(); + documentTmpl->Set(v8::String::New("createElement"), v8::FunctionTemplate::New(jsDocumentCreateElement, v8::External::New(reinterpret_cast(_interpreter)))); + documentTmpl->Set(v8::String::New("evaluate"), v8::FunctionTemplate::New(jsDocumentEvaluate, v8::External::New(reinterpret_cast(_interpreter)))); + return documentTmpl; + } + + v8::Handle V8SCXMLDOM::jsDocumentCreateElement(const v8::Arguments& args) { + assert(!args.Data().IsEmpty()); + assert(args.Data()->IsExternal()); + + Interpreter* interpreter = static_cast(v8::External::Unwrap(args.Data())); + v8::Handle elementTmpl = v8::ObjectTemplate::New(); + elementTmpl->SetAccessor(v8::String::New("tagName"), V8SCXMLDOM::jsElementTagName); + elementTmpl->Set(v8::String::New("getAttribute"), v8::FunctionTemplate::New(jsElementGetAttribute)); + elementTmpl->Set(v8::String::New("setAttribute"), v8::FunctionTemplate::New(jsElementSetAttribute)); + elementTmpl->SetInternalFieldCount(1); + v8::Handle elementJS = elementTmpl->NewInstance(); + + assert(args.Length() == 1); + assert(args[0]->IsString()); + + v8::String::AsciiValue tagName(args[0]); + Element* element = new Element(interpreter->getDocument().createElement(*tagName)); + + elementJS->SetInternalField(0, v8::External::New(element)); + return elementJS; + } + + v8::Handle V8SCXMLDOM::jsDocumentEvaluate(const v8::Arguments& args) { + assert(!args.Data().IsEmpty()); + assert(args.Data()->IsExternal()); + + assert(args.Length() > 0); + assert(args[0]->IsString()); + + + Interpreter* interpreter = static_cast(v8::External::Unwrap(args.Data())); + Node context; + if (args.Length() > 1) { + assert(args[1]->ToObject()->InternalFieldCount() == 1); + context = *static_cast*>(v8::Local::Cast(args[1]->ToObject()->GetInternalField(0))->Value()); + } else { + context = interpreter->getDocument(); + } + v8::String::AsciiValue xpathExpr(args[0]); + XPathValue* xpathValue = new XPathValue(interpreter->getXPath().evaluate(*xpathExpr, context)); + + v8::Handle xpathValueJS = getXPathValueTmpl()->NewInstance(); + xpathValueJS->SetInternalField(0, v8::External::New(xpathValue)); + return xpathValueJS; + } + + v8::Handle V8SCXMLDOM::jsElementTagName(v8::Local property, + const v8::AccessorInfo &info) { + Element* element = static_cast*>(v8::Local::Cast(info.Holder()->GetInternalField(0))->Value()); + return v8::String::New(element->getTagName().c_str()); + } + + v8::Handle V8SCXMLDOM::jsElementGetAttribute(const v8::Arguments& args) { + assert(!args.Data().IsEmpty()); + assert(args.Data()->IsExternal()); + + assert(args.Length() == 1); + assert(args[0]->IsString()); + + Element* element = static_cast*>(v8::External::Unwrap(args.Data())); + + v8::String::AsciiValue attribute(args[0]); + if (element->hasAttribute(*attribute)) { + return v8::String::New(element->getAttribute(*attribute).c_str()); + } + return v8::String::New(""); + } + + v8::Handle V8SCXMLDOM::jsElementSetAttribute(const v8::Arguments& args) { + v8::Local self = args.Holder(); + assert(self->InternalFieldCount() == 1); + + assert(args.Length() == 2); + assert(args[0]->IsString()); + assert(args[1]->IsString()); + + v8::String::AsciiValue attribute(args[0]); + v8::String::AsciiValue value(args[1]); + + Element* element = static_cast*>(v8::External::Unwrap(self->GetInternalField(0))); + element->setAttribute(*attribute, *value); + return v8::Undefined(); + } + + v8::Handle V8SCXMLDOM::jsXPathValueAsNodeSet(const v8::Arguments& args) { + v8::Local self = args.Holder(); + assert(self->InternalFieldCount() == 1); + XPathValue* xPathValue = static_cast*>(v8::External::Unwrap(self->GetInternalField(0))); + + v8::Persistent nodeSetJS = v8::Persistent::New(getNodeSetTmpl()->NewInstance()); + nodeSetJS->SetInternalField(0, v8::External::New(new NodeSet(xPathValue->asNodeSet()))); + nodeSetJS.MakeWeak(NULL, jsNodeSetDestructor); + return nodeSetJS; + + } + + void V8SCXMLDOM::jsNodeSetDestructor(v8::Persistent object, void* data) { + NodeSet* nodeSet = static_cast*>(v8::Local::Cast(object->ToObject()->GetInternalField(0))->Value()); + delete nodeSet; + } + + v8::Handle V8SCXMLDOM::jsNodeSetGetIndex(uint32_t index, const v8::AccessorInfo &info) { + v8::Local self = info.Holder(); + assert(self->InternalFieldCount() == 1); + NodeSet* nodeSet = static_cast*>(v8::Local::Cast(info.Holder()->GetInternalField(0))->Value()); + + if (nodeSet->size() >= index) { + Node* node = new Node((*nodeSet)[index]); + v8::Handle nodeJS = getNodeTmpl()->NewInstance(); + nodeJS->SetInternalField(0, v8::External::New(node)); + return nodeJS; + } + return v8::Undefined(); + } + + v8::Handle V8SCXMLDOM::jsNodeSetLength(const v8::Arguments& args) { + v8::Local self = args.Holder(); + assert(self->InternalFieldCount() == 1); + NodeSet* nodeSet = static_cast*>(v8::External::Unwrap(self->GetInternalField(0))); + return v8::Integer::New(nodeSet->size()); + } + + v8::Handle V8SCXMLDOM::jsNodeAppendChild(const v8::Arguments& args) { + v8::Local self = args.Holder(); + assert(self->InternalFieldCount() == 1); + Node* node = static_cast*>(v8::External::Unwrap(self->GetInternalField(0))); + + assert(args.Length() == 1); + assert(args[0]->IsObject()); + + Node* childToAppend = static_cast*>(v8::External::Unwrap(args[0]->ToObject()->GetInternalField(0))); + node->appendChild(*childToAppend); + +// std::cout << *childToAppend << std::endl; + + return v8::Undefined(); + } + + v8::Handle V8SCXMLDOM::xPathValueTmpl; + v8::Handle V8SCXMLDOM::getXPathValueTmpl() { + if (xPathValueTmpl.IsEmpty()) { + xPathValueTmpl = v8::ObjectTemplate::New(); + xPathValueTmpl->SetInternalFieldCount(1); + xPathValueTmpl->Set(v8::String::New("asNodeSet"), v8::FunctionTemplate::New(jsXPathValueAsNodeSet)); + } + return xPathValueTmpl; + } + + v8::Handle V8SCXMLDOM::nodeSetTmpl; + v8::Handle V8SCXMLDOM::getNodeSetTmpl() { + if (nodeSetTmpl.IsEmpty()) { + nodeSetTmpl = v8::ObjectTemplate::New(); + nodeSetTmpl->SetInternalFieldCount(1); + nodeSetTmpl->SetIndexedPropertyHandler(jsNodeSetGetIndex); + nodeSetTmpl->Set(v8::String::New("length"), v8::FunctionTemplate::New(jsNodeSetLength)); + } + return nodeSetTmpl; + } + + v8::Handle V8SCXMLDOM::nodeTmpl; + v8::Handle V8SCXMLDOM::getNodeTmpl() { + if (nodeTmpl.IsEmpty()) { + nodeTmpl = v8::ObjectTemplate::New(); + nodeTmpl->SetInternalFieldCount(1); + nodeTmpl->Set(v8::String::New("appendChild"), v8::FunctionTemplate::New(jsNodeAppendChild)); + } + return nodeTmpl; + } + +} \ No newline at end of file diff --git a/src/uscxml/datamodel/ecmascript/v8/dom/V8SCXMLDOM.h b/src/uscxml/datamodel/ecmascript/v8/dom/V8SCXMLDOM.h new file mode 100644 index 0000000..75920da --- /dev/null +++ b/src/uscxml/datamodel/ecmascript/v8/dom/V8SCXMLDOM.h @@ -0,0 +1,58 @@ +#ifndef V8SCXMLDOM_H_AREM0ZC4 +#define V8SCXMLDOM_H_AREM0ZC4 + +#include "uscxml/Interpreter.h" + +#include +#include + +#include + +namespace uscxml { + +class V8SCXMLDOM { +public: + V8SCXMLDOM(Interpreter* interpreter); + virtual ~V8SCXMLDOM() {}; + + v8::Handle getDocument(); + static v8::Handle jsDocumentCreateElement(const v8::Arguments& args); + static v8::Handle jsDocumentEvaluate(const v8::Arguments& args); + + static v8::Handle jsElementTagName(v8::Local property, const v8::AccessorInfo &info); + static v8::Handle jsElementGetAttribute(const v8::Arguments& args); + static v8::Handle jsElementSetAttribute(const v8::Arguments& args); + + static v8::Handle jsXPathValueAsNodeSet(const v8::Arguments& args); + + static v8::Handle jsNodeSetGetIndex(uint32_t index, const v8::AccessorInfo &info); + static v8::Handle jsNodeSetLength(const v8::Arguments& args); + static void jsNodeSetDestructor(v8::Persistent object, void* data); + + + static v8::Handle jsNodeAppendChild(const v8::Arguments& args); + + static v8::Handle getXPathValueTmpl(); + static v8::Handle getNodeSetTmpl(); + static v8::Handle getNodeTmpl(); + + Interpreter* _interpreter; + static v8::Handle xPathValueTmpl; + static v8::Handle nodeSetTmpl; + static v8::Handle nodeTmpl; + +}; + +class V8Node { +}; + +class V8DOMDocument { + V8DOMDocument(); + virtual ~V8DOMDocument(); + + v8::Handle jsChildNodes(); +}; + +} + +#endif /* end of include guard: V8SCXMLDOM_H_AREM0ZC4 */ diff --git a/src/uscxml/debug/SCXMLDotWriter.cpp b/src/uscxml/debug/SCXMLDotWriter.cpp new file mode 100644 index 0000000..cd947ce --- /dev/null +++ b/src/uscxml/debug/SCXMLDotWriter.cpp @@ -0,0 +1,358 @@ +#include "SCXMLDotWriter.h" +#include "uscxml/Interpreter.h" +#include // replace_all + +namespace uscxml { + +using namespace Arabica::DOM; + +int SCXMLDotWriter::_indentation = 0; + +SCXMLDotWriter::SCXMLDotWriter(Interpreter* interpreter) { + _interpreter = interpreter; +} + +SCXMLDotWriter::~SCXMLDotWriter() { + +} + +std::string SCXMLDotWriter::getPrefix() { + std::string prefix = ""; + for (int i = 0; i < _indentation; i++) + prefix += " "; + return prefix; +} + +void SCXMLDotWriter::toDot(const std::string& filename, Interpreter* interpreter) { + std::ofstream outfile(filename.c_str()); + NodeList scxmlElems = interpreter->getDocument().getElementsByTagName("scxml"); + SCXMLDotWriter writer(interpreter); + if (scxmlElems.getLength() > 0) { + _indentation++; + outfile << "digraph {" << std::endl; + outfile << "rankdir=LR;" << std::endl; + writer.writeSCXMLElement(outfile, (Arabica::DOM::Element)scxmlElems.item(0)); + _indentation--; + outfile << "}" << std::endl; + } + +} + +void SCXMLDotWriter::writeSCXMLElement(std::ostream& os, const Arabica::DOM::Element& elem) { + writeStateElement(os, elem); + +// std::string elemId = idForNode(elem); +// os << getPrefix() << "subgraph \"cluster" << elemId.substr(1, elemId.length() - 1) << " {" << std::endl; +// _indentation++; +// os << getPrefix() << "label=\"" << nameForNode(elem) << "\"" << std::endl; +// writeStateElement(os, (Arabica::DOM::Element)_interpreter->getInitialState()); +// os << getPrefix() << "} " << std::endl; + +} + +void SCXMLDotWriter::writeStateElement(std::ostream& os, const Arabica::DOM::Element& elem) { + + std::string elemId = idForNode(elem); + NodeList childElems = elem.getChildNodes(); + + if (_knownIds.find(elemId) != _knownIds.end()) + return; + _knownIds.insert(elemId); + + bool subgraph = Interpreter::isCompound(elem) || Interpreter::isParallel(elem); + if (subgraph) { + _indentation++; + os << getPrefix() << "subgraph \"cluster_" << elemId << "\" {" << std::endl; + os << getPrefix() << "label=\"" << nameForNode(elem) << "\\l\"" << std::endl; + } + + os << getPrefix() << "\"" << elemId << "\"["; + os << "label=<State
" << nameForNode(elem) << ">,"; + if (_interpreter->isInitial(elem)) + os << "style=filled,"; + if (_interpreter->isFinal(elem)) + os << "shape=doublecircle,"; + os << "];" << std::endl; + + std::string details = getDetailedLabel(elem); +// std::cout << details << std::endl; + + if (details.size() > 0) { + os << getPrefix() << "\"" << elemId << "Exec\"["; +// os << "fontsize=10,"; + os << "shape=box,"; + os << "color=grey,"; + os << "label=<" << details << ">"; + os << "]" << std::endl; + os << getPrefix() << "\"" << elemId << "\" -> \"" << elemId << "Exec\" [arrowhead=none, color=grey]" << std::endl; + } + +// NodeList childElems = elem.getChildNodes(); +// for (int i = 0; i < childElems.getLength(); i++) { +// if (Interpreter::isState(childElems.item(i))) { +// writeStateElement(os, (Arabica::DOM::Element)childElems.item(i)); +// } +// } + + for (int i = 0; i < childElems.getLength(); i++) { + if (childElems.item(i).getNodeType() == Node_base::ELEMENT_NODE && boost::iequals(TAGNAME(childElems.item(i)), "transition")) { + writeTransitionElement(os, (Arabica::DOM::Element)childElems.item(i)); + os << getPrefix() << "\"" << elemId << "\" -> \"" << idForNode(childElems.item(i)) << "\"" << std::endl; + } + if (Interpreter::isState(childElems.item(i))) { + writeStateElement(os, (Arabica::DOM::Element)childElems.item(i)); + } + if (childElems.item(i).getNodeType() == Node_base::ELEMENT_NODE && boost::iequals(TAGNAME(childElems.item(i)), "initial")) { + NodeList grandChildElems = childElems.item(i).getChildNodes(); + for (int j = 0; j < grandChildElems.getLength(); j++) { + if (grandChildElems.item(j).getNodeType() == Node_base::ELEMENT_NODE && boost::iequals(TAGNAME(grandChildElems.item(j)), "transition")) { + writeTransitionElement(os, (Arabica::DOM::Element)grandChildElems.item(j)); + os << getPrefix() << "\"" << elemId << "\" -> \"" << idForNode(grandChildElems.item(j)) << "\"" << std::endl; + } + } + } + } + + if (subgraph) { + _indentation--; + os << getPrefix() << "} " << std::endl; + } + +} + +void SCXMLDotWriter::writeTransitionElement(std::ostream& os, const Arabica::DOM::Element& elem) { + std::string elemId = idForNode(elem); + + Arabica::XPath::NodeSet targetStates = _interpreter->getTargetStates(elem); + + std::string label; + os << getPrefix() << "\"" << elemId << "\"["; +// os << "fontsize=10,"; + os << "shape=box,"; + os << "label=<Transition
"; + if (HAS_ATTR(elem, "event")) + os << "event: " << ATTR(elem, "event"); + if (HAS_ATTR(elem, "cond")) + os << "cond: " << ATTR(elem, "cond"); + if (!HAS_ATTR(elem, "cond") && !HAS_ATTR(elem, "event")) + os << "unconditional"; + os << ">"; + os << "]" << std::endl; + + for (int i = 0; i < targetStates.size(); i++) { + os << getPrefix() << "\"" << elemId << "\" -> \"" << idForNode(targetStates[i]) << "\"" << std::endl; + writeStateElement(os, (Arabica::DOM::Element)targetStates[i]); + } + +} + +std::string SCXMLDotWriter::getDetailedLabel(const Arabica::DOM::Element& elem, int indentation) { + +/* + + + + + + + + +
onEntry
Details + Nested Content +
+*/ + + std::list content; + + NodeList childElems = elem.getChildNodes(); + for (int i = 0; i < childElems.getLength(); i++) { + if (childElems.item(i).getNodeType() != Node_base::ELEMENT_NODE) + continue; + + if (Interpreter::isState(childElems.item(i)) || + boost::iequals(TAGNAME(childElems.item(i)), "transition") || + boost::iequals(TAGNAME(childElems.item(i)), "initial") || + false) + continue; + + struct ElemDetails details; + details.name = "" + TAGNAME(childElems.item(i)) + ""; + + // provide details for special elements here + + // param --------- + if (boost::iequals(TAGNAME(childElems.item(i)), "param")) { + if (HAS_ATTR(childElems.item(i), "name")) + details.name += " " + ATTR(childElems.item(i), "name") + " = "; + if (HAS_ATTR(childElems.item(i), "expr")) + details.name += ATTR(childElems.item(i), "expr"); + if (HAS_ATTR(childElems.item(i), "location")) + details.name += ATTR(childElems.item(i), "location"); + } + + // data --------- + if (boost::iequals(TAGNAME(childElems.item(i)), "data")) { + if (HAS_ATTR(childElems.item(i), "id")) + details.name += " " + ATTR(childElems.item(i), "id") + " = "; + if (HAS_ATTR(childElems.item(i), "src")) + details.name += ATTR(childElems.item(i), "src"); + if (HAS_ATTR(childElems.item(i), "expr")) + details.name += ATTR(childElems.item(i), "expr"); + NodeList grandChildElems = childElems.item(i).getChildNodes(); + for (int j = 0; j < grandChildElems.getLength(); j++) { + if (grandChildElems.item(j).getNodeType() == Node_base::TEXT_NODE) { + details.name += dotEscape(grandChildElems.item(j).getNodeValue()); + } + } + } + + // invoke --------- + if (boost::iequals(TAGNAME(childElems.item(i)), "invoke")) { + if (HAS_ATTR(childElems.item(i), "type")) + details.name += "
type = " + ATTR(childElems.item(i), "type"); + if (HAS_ATTR(childElems.item(i), "typeexpr")) + details.name += "
type = " + ATTR(childElems.item(i), "typeexpr"); + if (HAS_ATTR(childElems.item(i), "src")) + details.name += "
src = " + ATTR(childElems.item(i), "src"); + if (HAS_ATTR(childElems.item(i), "srcexpr")) + details.name += "
src = " + ATTR(childElems.item(i), "srcexpr"); + if (HAS_ATTR(childElems.item(i), "id")) + details.name += "
id = " + ATTR(childElems.item(i), "id"); + if (HAS_ATTR(childElems.item(i), "idlocation")) + details.name += "
id = " + ATTR(childElems.item(i), "idlocation"); + } + + // send --------- + if (boost::iequals(TAGNAME(childElems.item(i)), "send")) { + if (HAS_ATTR(childElems.item(i), "type")) + details.name += "
type = " + ATTR(childElems.item(i), "type"); + if (HAS_ATTR(childElems.item(i), "typeexpr")) + details.name += "
type = " + ATTR(childElems.item(i), "typeexpr"); + if (HAS_ATTR(childElems.item(i), "event")) + details.name += "
event = " + ATTR(childElems.item(i), "event"); + if (HAS_ATTR(childElems.item(i), "eventexpr")) + details.name += "
event = " + ATTR(childElems.item(i), "eventexpr"); + if (HAS_ATTR(childElems.item(i), "target")) + details.name += "
target = " + ATTR(childElems.item(i), "target"); + if (HAS_ATTR(childElems.item(i), "targetexpr")) + details.name += "
target = " + ATTR(childElems.item(i), "targetexpr"); + if (HAS_ATTR(childElems.item(i), "delay")) + details.name += "
delay = " + ATTR(childElems.item(i), "delay"); + if (HAS_ATTR(childElems.item(i), "delayexpr")) + details.name += "
delay = " + ATTR(childElems.item(i), "delayexpr"); + } + + // script --------- + if (boost::iequals(TAGNAME(childElems.item(i)), "script")) { + details.name += " "; + if (HAS_ATTR(childElems.item(i), "src")) + details.name += ATTR(childElems.item(i), "src"); + NodeList grandChildElems = childElems.item(i).getChildNodes(); + for (int j = 0; j < grandChildElems.getLength(); j++) { + if (grandChildElems.item(j).getNodeType() == Node_base::TEXT_NODE) { + details.name += dotEscape(grandChildElems.item(j).getNodeValue()); + } + } + } + + // recurse + details.content = getDetailedLabel((Arabica::DOM::Element)childElems.item(i), indentation + 1); + content.push_back(details); + } + + std::stringstream ssContent; + + if (content.size() > 0) { + ssContent << ""; + + std::list::iterator contentIter = content.begin(); + while(contentIter != content.end()) { + ssContent << ""; +// ssContent << ""; + ssContent << ""; + ssContent << ""; + + if (contentIter->content.size() > 0) { + ssContent << ""; +// ssContent << ""; + ssContent << ""; + ssContent << ""; + } + contentIter++; + + } + ssContent << "
" << contentIter->name << "" << contentIter->name << "
" << contentIter->details << "" << contentIter->content << "
"; + } + return ssContent.str(); +} + +std::string SCXMLDotWriter::dotEscape(const std::string& text) { + std::string escaped(text); + boost::replace_all(escaped, "", ""); + + return escaped; +} + +std::string SCXMLDotWriter::colorForIndent(int indent) { + int color = 255 - (16 * indent); + std::stringstream ss; + ss << std::hex << color; + ss << std::hex << color; + ss << std::hex << color; + return ss.str(); +} + +std::string SCXMLDotWriter::nameForNode(const Arabica::DOM::Node& node) { + std::string elemName; + if (node.getNodeType() == Node_base::ELEMENT_NODE) { + Arabica::DOM::Element elem = (Arabica::DOM::Element)node; + if (elem.hasAttribute("name")) { + elemName = elem.getAttribute("name"); + } else if (elem.hasAttribute("id")) { + elemName = elem.getAttribute("id"); + } + } + if (elemName.size() == 0) + elemName = boost::lexical_cast(node.getLocalName()); + + return elemName; + +} + +std::string SCXMLDotWriter::idForNode(const Arabica::DOM::Node& node) { + std::string elemId; + if (node.getNodeType() == Node_base::ELEMENT_NODE) { + Arabica::DOM::Element elem = (Arabica::DOM::Element)node; + if (elem.hasAttribute("name")) { + elemId = elem.getAttribute("name"); + } else if (elem.hasAttribute("id")) { + elemId = elem.getAttribute("id"); + } + } + if (elemId.size() == 0) { + Arabica::DOM::Node tmpParent = node; + Arabica::DOM::Node tmpIndex; + do { + if (tmpParent.getNodeType() != Node_base::ELEMENT_NODE) + continue; + + tmpIndex = tmpParent; + int index = 0; + + while((tmpIndex = tmpIndex.getPreviousSibling())) + index++; + + std::stringstream ssElemId; + ssElemId << TAGNAME(tmpParent) << index << "."; + elemId = ssElemId.str() + elemId; + } while ((tmpParent = tmpParent.getParentNode())); +// elemId = ssElemId.str(); + } + + std::replace(elemId.begin(), elemId.end(), '-', '_'); +// std::replace(elemId.begin(), elemId.end(), '.', '_'); + + return elemId; +} + +} \ No newline at end of file diff --git a/src/uscxml/debug/SCXMLDotWriter.h b/src/uscxml/debug/SCXMLDotWriter.h new file mode 100644 index 0000000..7ebb916 --- /dev/null +++ b/src/uscxml/debug/SCXMLDotWriter.h @@ -0,0 +1,45 @@ +#ifndef SCXMLDOTWRITER_H_AOP0OHXX +#define SCXMLDOTWRITER_H_AOP0OHXX + +#include +#include +#include + +namespace uscxml { + +class Interpreter; + +class SCXMLDotWriter { +public: + + struct ElemDetails { + std::string name; + std::string details; + std::string content; + }; + + SCXMLDotWriter(Interpreter* interpreter); + ~SCXMLDotWriter(); + + static void toDot(const std::string& filename, Interpreter* interpreter); + void writeSCXMLElement(std::ostream& os, const Arabica::DOM::Element& elem); + void writeStateElement(std::ostream& os, const Arabica::DOM::Element& elem); + void writeTransitionElement(std::ostream& os, const Arabica::DOM::Element& elem); + + std::string getDetailedLabel(const Arabica::DOM::Element& elem, int indentation = 0); + std::string colorForIndent(int indent); + + std::string idForNode(const Arabica::DOM::Node& node); + std::string nameForNode(const Arabica::DOM::Node& node); + + static std::string getPrefix(); + static std::string dotEscape(const std::string& text); + + Interpreter* _interpreter; + std::set _knownIds; + static int _indentation; +}; + +} + +#endif /* end of include guard: SCXMLDOTWRITER_H_AOP0OHXX */ diff --git a/src/uscxml/invoker/modality/MMIComponent.cpp b/src/uscxml/invoker/modality/MMIComponent.cpp new file mode 100644 index 0000000..22c2e17 --- /dev/null +++ b/src/uscxml/invoker/modality/MMIComponent.cpp @@ -0,0 +1,42 @@ +#include "MMIComponent.h" +#include "uscxml/Interpreter.h" + +namespace uscxml { + +MMIComponent::MMIComponent() { +} + + +MMIComponent::~MMIComponent() { +}; + +Invoker* MMIComponent::create(Interpreter* interpreter) { + MMIComponent* invoker = new MMIComponent(); + invoker->_interpreter = interpreter; + return invoker; +} + +Data MMIComponent::getDataModelVariables() { + Data data; + return data; +} + +void MMIComponent::send(SendRequest& req) { + assert(false); +} + +void MMIComponent::cancel(const std::string sendId) { + assert(false); +} + +void MMIComponent::sendToParent(SendRequest& req) { + req.invokeid = _invokeId; + assert(false); +} + +void MMIComponent::invoke(InvokeRequest& req) { + _invokeId = req.invokeid; + +} + +} \ No newline at end of file diff --git a/src/uscxml/invoker/modality/MMIComponent.h b/src/uscxml/invoker/modality/MMIComponent.h new file mode 100644 index 0000000..a83775f --- /dev/null +++ b/src/uscxml/invoker/modality/MMIComponent.h @@ -0,0 +1,109 @@ +#ifndef MMICOMPONENT_H_MZ1I550N +#define MMICOMPONENT_H_MZ1I550N + +#include "uscxml/Factory.h" + +namespace uscxml { + +class Interpreter; + +class MMIComponent : public Invoker { +public: + + enum State { + PAUSED, + RUNNING, + IDLE, + TERMINATED + }; + + MMIComponent(); + virtual ~MMIComponent(); + virtual Invoker* create(Interpreter* interpreter); + + virtual Data getDataModelVariables(); + virtual void send(SendRequest& req); + virtual void cancel(const std::string sendId); + virtual void invoke(InvokeRequest& req); + virtual void sendToParent(SendRequest& req); + +protected: + std::string _invokeId; + Interpreter* _interpreter; + + State _state; +}; + + +/** Base classes for MMI messages */ + +class MMICoreMessage { +public: + std::string source; + std::string target; + std::string data; + std::string requestId; +}; + +class MMICtxMessage : public MMICoreMessage { +public: + std::string context; +}; + +class MMIStartMessage : public MMICtxMessage { +public: + std::string content; + std::string contentURL; +}; + +class MMISimpleStatusMessage : public MMICtxMessage { +public: + std::string status; +}; + +class MMIStatusMessage : public MMISimpleStatusMessage { +public: + std::string statusInfo; +}; + +/** Concrete MMI messages */ + +class MMINewContextRequest : public MMICoreMessage {}; + +/***/ + +class MMIPauseRequest : public MMICtxMessage {}; +class MMIResumeRequest : public MMICtxMessage {}; +class MMICancelRequest : public MMICtxMessage {}; +class MMIClearContextRequest : public MMICtxMessage {}; +class MMIStatusRequest : public MMICtxMessage {}; + +/***/ + +class MMIStartRequest : public MMIStartMessage {}; +class MMIPrepareRequest : public MMIStartMessage {}; + +/***/ + +class MMIExtensionNotification : public MMICtxMessage { + std::string name; +}; + +/***/ + +class MMIStatusResponse : public MMISimpleStatusMessage {}; + +/***/ + +class MMIStartResponse : public MMIStatusMessage {}; +class MMIPrepareRespnse : public MMIStatusMessage {}; +class MMIPauseResponse : public MMIStatusMessage {}; +class MMIResumeResponse : public MMIStatusMessage {}; +class MMICancelResponse : public MMIStatusMessage {}; +class MMIDoneNotification : public MMIStatusMessage {}; +class MMINewContextResponse : public MMIStatusMessage {}; +class MMIClearContextResponse : public MMIStatusMessage {}; + +} + +#endif /* end of include guard: MMICOMPONENT_H_MZ1I550N */ diff --git a/src/uscxml/invoker/modality/UmundoComponent.cpp b/src/uscxml/invoker/modality/UmundoComponent.cpp new file mode 100644 index 0000000..22dd279 --- /dev/null +++ b/src/uscxml/invoker/modality/UmundoComponent.cpp @@ -0,0 +1,49 @@ +#include "UmundoComponent.h" +#include "uscxml/Interpreter.h" + +namespace uscxml { + +UmundoComponent::UmundoComponent() { +} + + +UmundoComponent::~UmundoComponent() { + delete _invokedInterpreter; +}; + +Invoker* UmundoComponent::create(Interpreter* interpreter) { + UmundoComponent* invoker = new UmundoComponent(); + invoker->_parentInterpreter = interpreter; + return invoker; +} + +Data UmundoComponent::getDataModelVariables() { + Data data; + return data; +} + +void UmundoComponent::send(SendRequest& req) { + assert(false); +} + +void UmundoComponent::cancel(const std::string sendId) { + assert(false); +} + +void UmundoComponent::sendToParent(SendRequest& req) { + req.invokeid = _invokeId; + _parentInterpreter->receive(req); +} + +void UmundoComponent::invoke(InvokeRequest& req) { + _invokeId = req.invokeid; + _invokedInterpreter = Interpreter::fromURI(req.src); + DataModel* dataModel = _invokedInterpreter->getDataModel(); + if (dataModel != NULL) { + + } + _invokedInterpreter->setInvoker(this); + _invokedInterpreter->start(); +} + +} \ No newline at end of file diff --git a/src/uscxml/invoker/modality/UmundoComponent.h b/src/uscxml/invoker/modality/UmundoComponent.h new file mode 100644 index 0000000..f2c76c4 --- /dev/null +++ b/src/uscxml/invoker/modality/UmundoComponent.h @@ -0,0 +1,30 @@ +#ifndef UMUNDOCOMPONENT_H_VMW54W1R +#define UMUNDOCOMPONENT_H_VMW54W1R + +#include "MMIComponent.h" + +namespace uscxml { + +class Interpreter; + +class UmundoComponent : public MMIComponent { +public: + UmundoComponent(); + virtual ~UmundoComponent(); + virtual Invoker* create(Interpreter* interpreter); + + virtual Data getDataModelVariables(); + virtual void send(SendRequest& req); + virtual void cancel(const std::string sendId); + virtual void invoke(InvokeRequest& req); + virtual void sendToParent(SendRequest& req); + +protected: + std::string _invokeId; + Interpreter* _invokedInterpreter; + Interpreter* _parentInterpreter; +}; + +} + +#endif /* end of include guard: UMUNDOCOMPONENT_H_VMW54W1R */ diff --git a/src/uscxml/invoker/modality/miles/SpatialAudio.cpp b/src/uscxml/invoker/modality/miles/SpatialAudio.cpp new file mode 100644 index 0000000..559bcaa --- /dev/null +++ b/src/uscxml/invoker/modality/miles/SpatialAudio.cpp @@ -0,0 +1,153 @@ +#include "SpatialAudio.h" +#include "uscxml/Interpreter.h" + +#include + +#include + +namespace uscxml { + +SpatialAudio::SpatialAudio() { + _audioDevOpen = false; + _audioDev = NULL; + _audioDevIndex = -1; + _pos = new float[3]; + _pos[0] = _pos[1] = _pos[2] = 0.0; +} + + +SpatialAudio::~SpatialAudio() { +}; + +Invoker* SpatialAudio::create(Interpreter* interpreter) { + SpatialAudio* invoker = new SpatialAudio(); + invoker->_interpreter = interpreter; + return invoker; +} + +Data SpatialAudio::getDataModelVariables() { + Data data; + return data; +} + +void SpatialAudio::send(SendRequest& req) { + setPosFromParams(req.params); + if (boost::iequals(req.name, "play")) { + if (!_audioDevOpen) { + _audioDev = miles_audio_device_open(_audioDevIndex, 0, 22050, 2, 1, 0); + } + if (_audioDev != NULL) { + _audioDevOpen = true; + miles_audio_device_control(_audioDev, MILES_AUDIO_DEVICE_CTRL_SET_POSITION, _pos); + + char* buffer = (char*)malloc(_audioDev->chunk_size); + // skip wav header + url_fread(buffer, 44, 1, _urlHandle); + int read = 0; + while((read = url_fread(buffer, _audioDev->chunk_size, 1, _urlHandle) != 0)) { + miles_audio_device_write(_audioDev, buffer, _audioDev->chunk_size); + } + url_rewind(_urlHandle); + free(buffer); + } + } +} + +void SpatialAudio::cancel(const std::string sendId) { + assert(false); +} + +void SpatialAudio::sendToParent(SendRequest& req) { + req.invokeid = _invokeId; + assert(false); +} + +void SpatialAudio::invoke(InvokeRequest& req) { + _invokeId = req.invokeid; + + if (req.src.length() > 0) { + _urlHandle = url_fopen(req.src.c_str(), "r"); + if(!_urlHandle) { + LOG(ERROR) << "couldn't url_fopen() " << req.src; + } + } + + setPosFromParams(req.params); + + struct miles_audio_device_description *devices; + int ndevs; + + ndevs = miles_audio_device_get_supported_devices(&devices); + + for (int i = 0; i < ndevs; i++) { + if ((devices[i].capabilities & MILES_AUDIO_DEVICE_CAPABILITY_SPATIAL) && + (devices[i].capabilities & MILES_AUDIO_DEVICE_CAPABILITY_OUTPUT)) { + _audioDevIndex = i; + break; + } + } +} + +void SpatialAudio::setPosFromParams(std::map& params) { + // vector explicitly given + try { + if (params.find("x") != params.end()) + _pos[0] = boost::lexical_cast(params["x"]); + if (params.find("y") != params.end()) + _pos[1] = boost::lexical_cast(params["y"]); + if (params.find("z") != params.end()) + _pos[2] = boost::lexical_cast(params["z"]); + } catch (boost::bad_lexical_cast& e) { + LOG(ERROR) << "Cannot interpret x, y or z as float value in params: " << e.what(); + } + + try { + // right is an alias for x + if (params.find("right") != params.end()) + _pos[0] = boost::lexical_cast(params["right"]); + // height is an alias for y + if (params.find("height") != params.end()) + _pos[1] = boost::lexical_cast(params["height"]); + // front is an alias for z + if (params.find("front") != params.end()) + _pos[2] = boost::lexical_cast(params["front"]); + } catch (boost::bad_lexical_cast& e) { + LOG(ERROR) << "Cannot interpret right, height or front as float value in params: " << e.what(); + } + + // do we have a position on a circle? + try { + if (params.find("circle") != params.end()) { + float rad = posToRadian(params["circle"]); + _pos[0] = cosf(rad); + _pos[2] = -1 * sinf(rad); // z axis increases to front + } + } catch (boost::bad_lexical_cast& e) { + LOG(ERROR) << "Cannot interpret circle as float value in params: " << e.what(); + } +// std::cout << _pos[0] << ":" << _pos[1] << ":" << _pos[2] << std::endl; + +} + +float SpatialAudio::posToRadian(std::string& position) { + boost::trim(position); + float rad = 0; + + if (position.size() > 3 && boost::iequals("deg", position.substr(position.length() - 3, 3))) { + rad = boost::lexical_cast(position.substr(0, position.size() - 3)); + rad = fmodf(rad, 360); // into range [0-360] + rad /= 180; // into range [0-2] + rad *= M_PI; // into range [0-2PI] + rad -= M_PI_2; // 0 to top; + rad *= -1; // make clockwise + rad += 2 * M_PI; // make positive + } else if (position.size() > 3 && boost::iequals("rad", position.substr(position.length() - 3, 3))) { + rad = boost::lexical_cast(position.substr(0, position.size() - 3)); + rad = fmodf(rad, M_PI * 2); // into range [0-2*PI] + } else { + LOG(ERROR) << "Cannot make sense of position value " << position << ": does not end in 'deg', 'rad'"; + } + return rad; +} + +} \ No newline at end of file diff --git a/src/uscxml/invoker/modality/miles/SpatialAudio.h b/src/uscxml/invoker/modality/miles/SpatialAudio.h new file mode 100644 index 0000000..926c82a --- /dev/null +++ b/src/uscxml/invoker/modality/miles/SpatialAudio.h @@ -0,0 +1,49 @@ +#ifndef SPATIALAUDIO_H_EH11SAQC +#define SPATIALAUDIO_H_EH11SAQC + +#include + +#include "uscxml/Utilities.h" +#include "../MMIComponent.h" + +extern "C" { +# include "miles/audio.h" +# include "miles/audio_codec.h" +# include "miles/audio_device.h" +} + +namespace uscxml { + +class Interpreter; + +class SpatialAudio : public MMIComponent { +public: + SpatialAudio(); + virtual ~SpatialAudio(); + virtual Invoker* create(Interpreter* interpreter); + + virtual Data getDataModelVariables(); + virtual void send(SendRequest& req); + virtual void cancel(const std::string sendId); + virtual void invoke(InvokeRequest& req); + virtual void sendToParent(SendRequest& req); + + void setPosFromParams(std::map& params); + static float posToRadian(std::string& position); + +protected: + std::string _invokeId; + Interpreter* _invokedInterpreter; + + URL_FILE* _urlHandle; + + float* _pos; + bool _audioDevOpen; + int _audioDevIndex; + struct miles_audio_device* _audioDev; + +}; + +} + +#endif /* end of include guard: SPATIALAUDIO_H_EH11SAQC */ diff --git a/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp b/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp index d0adcb3..13052c7 100644 --- a/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp +++ b/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp @@ -69,7 +69,7 @@ void EventIOProcessor::send(SendRequest& req) { const char* hostName = evhttp_uri_get_host(targetURI); // use synchronous dns resolving for multicast dns - if(strlen(hostName) >= strlen(".local")) { + if(hostName && strlen(hostName) >= strlen(".local")) { if(strcmp(hostName + strlen(hostName) - strlen(".local"), ".local") == 0) { evhttp_uri_set_host(targetURI, EventIOServer::syncResolve(hostName).c_str()); } diff --git a/test/src/audio/click.wav b/test/src/audio/click.wav new file mode 100644 index 0000000..e11b0b7 Binary files /dev/null and b/test/src/audio/click.wav differ diff --git a/test/src/scxml-gui-test.scxml b/test/src/scxml-gui-test.scxml new file mode 100644 index 0000000..adf93b8 --- /dev/null +++ b/test/src/scxml-gui-test.scxml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/src/test-completion.cpp b/test/src/test-completion.cpp index 33056f3..619bdef 100644 --- a/test/src/test-completion.cpp +++ b/test/src/test-completion.cpp @@ -1,4 +1,5 @@ #include "uscxml/Interpreter.h" +#include "uscxml/debug/SCXMLDotWriter.h" #include int main(int argc, char** argv) { @@ -10,7 +11,10 @@ int main(int argc, char** argv) { using namespace uscxml; Interpreter* interpreter = Interpreter::fromURI(argv[1]); + SCXMLDotWriter::toDot("output.dot", interpreter); + interpreter->interpret(); + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/test/src/test-dom.scxml b/test/src/test-dom.scxml new file mode 100644 index 0000000..4624209 --- /dev/null +++ b/test/src/test-dom.scxml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/src/test-spatial-audio.scxml b/test/src/test-spatial-audio.scxml new file mode 100644 index 0000000..f800c82 --- /dev/null +++ b/test/src/test-spatial-audio.scxml @@ -0,0 +1,68 @@ + + + + + { + 'id2': { + 'degree': 90 + } + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- cgit v0.12