From 4f6cbe9e7aec2b4a6c8f286f9097abfb011a6235 Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Fri, 13 Jan 2017 17:47:44 +0100 Subject: First support for serialization and some bug fixes for DOM per data.src --- CMakeLists.txt | 3 + contrib/src/jsmn/jsmn.c | 3 +- contrib/src/uscxml/PausableDelayedEventQueue.cpp | 94 +++++++++++++ contrib/src/uscxml/PausableDelayedEventQueue.h | 53 +++++++ src/uscxml/Interpreter.cpp | 31 ++++- src/uscxml/Interpreter.h | 15 ++ src/uscxml/interpreter/BasicContentExecutor.cpp | 70 ++++++++-- src/uscxml/interpreter/BasicContentExecutor.h | 2 +- src/uscxml/interpreter/BasicDelayedEventQueue.cpp | 49 +++++++ src/uscxml/interpreter/BasicDelayedEventQueue.h | 3 + src/uscxml/interpreter/BasicEventQueue.cpp | 19 +++ src/uscxml/interpreter/BasicEventQueue.h | 2 + src/uscxml/interpreter/ContentExecutor.cpp | 4 +- src/uscxml/interpreter/ContentExecutor.h | 2 +- src/uscxml/interpreter/ContentExecutorImpl.h | 2 +- src/uscxml/interpreter/EventQueue.cpp | 8 ++ src/uscxml/interpreter/EventQueue.h | 4 + src/uscxml/interpreter/EventQueueImpl.h | 6 + src/uscxml/interpreter/FastMicroStep.cpp | 152 +++++++++++++++------ src/uscxml/interpreter/FastMicroStep.h | 3 + src/uscxml/interpreter/InterpreterImpl.cpp | 140 ++++++++++++++++++- src/uscxml/interpreter/InterpreterImpl.h | 39 +++--- src/uscxml/interpreter/InterpreterMonitor.h | 3 +- src/uscxml/interpreter/MicroStep.cpp | 8 ++ src/uscxml/interpreter/MicroStep.h | 7 + src/uscxml/interpreter/MicroStepImpl.h | 6 +- src/uscxml/messages/Data.cpp | 143 ++++++++++++++----- src/uscxml/messages/Data.h | 10 +- src/uscxml/messages/Event.cpp | 58 +++++++- src/uscxml/messages/Event.h | 18 ++- src/uscxml/plugins/Invoker.cpp | 9 ++ src/uscxml/plugins/Invoker.h | 5 + src/uscxml/plugins/InvokerImpl.h | 13 ++ src/uscxml/plugins/datamodel/CMakeLists.txt | 2 +- .../ecmascript/JavaScriptCore/JSCDataModel.cpp | 18 ++- src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp | 41 ++++-- src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h | 7 + src/uscxml/util/DOM.cpp | 2 + test/CMakeLists.txt | 3 +- test/src/test-extensions.cpp | 99 +++----------- test/src/test-serialization.cpp | 147 ++++++++++++++++++++ test/src/test-url.cpp | 17 ++- 42 files changed, 1086 insertions(+), 234 deletions(-) create mode 100644 contrib/src/uscxml/PausableDelayedEventQueue.cpp create mode 100644 contrib/src/uscxml/PausableDelayedEventQueue.h create mode 100644 test/src/test-serialization.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b06de8b..f383edc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -400,6 +400,9 @@ if (WITH_DM_C89) endif() OPTION(WITH_DM_PROMELA "Do build with promela datamodel support" ON) +if (${CMAKE_CXX_COMPILER_ID} STREQUAL Clang) + SET_SOURCE_FILES_PROPERTIES(src/uscxml/plugins/datamodel/promela/parser/promela.lex.yy.cpp PROPERTIES COMPILE_FLAGS -Wno-deprecated-register ) +endif() add_subdirectory(src/uscxml) add_subdirectory(src/bindings) diff --git a/contrib/src/jsmn/jsmn.c b/contrib/src/jsmn/jsmn.c index 1b918f5..3352214 100644 --- a/contrib/src/jsmn/jsmn.c +++ b/contrib/src/jsmn/jsmn.c @@ -191,7 +191,8 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, } } /* Error if unmatched closing bracket */ - if (i == -1) return JSMN_ERROR_INVAL; + if (i == -1) + return JSMN_ERROR_INVAL; for (; i >= 0; i--) { token = &tokens[i]; if (token->start != -1 && token->end == -1) { diff --git a/contrib/src/uscxml/PausableDelayedEventQueue.cpp b/contrib/src/uscxml/PausableDelayedEventQueue.cpp new file mode 100644 index 0000000..cb7a373 --- /dev/null +++ b/contrib/src/uscxml/PausableDelayedEventQueue.cpp @@ -0,0 +1,94 @@ +/** + * @file + * @author 2017 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#include "PausableDelayedEventQueue.h" +#include + +namespace uscxml { + +PausableDelayedEventQueue::PausableDelayedEventQueue(DelayedEventQueueCallbacks* callbacks) : BasicDelayedEventQueue(callbacks) { + _pausedAt.tv_sec = 0; + _pausedAt.tv_usec = 0; +} + +std::shared_ptr PausableDelayedEventQueue::create(DelayedEventQueueCallbacks* callbacks) { + return std::shared_ptr(new PausableDelayedEventQueue(callbacks)); +} + +void PausableDelayedEventQueue::pause() { + if(_pausedAt.tv_sec != 0 || _pausedAt.tv_usec != 0) { + return; // we are already paused! + } + + evutil_gettimeofday(&_pausedAt, NULL); // remember when we paused + + { + // Verbatim copy of stop() without cancelAllDelayed() + if (_isStarted) { + _isStarted = false; + event_base_loopbreak(_eventLoop); + } + if (_thread) { + _thread->join(); + delete _thread; + _thread = NULL; + } + } + + std::lock_guard lock(_mutex); + + // remove all events from libevent without deleting them + for(auto callbackData : _callbackData) { + Event data = callbackData.second.userData; + event_del(callbackData.second.event); + } +} + +void PausableDelayedEventQueue::resume() { + if (_pausedAt.tv_sec != 0 || _pausedAt.tv_usec != 0) { + struct timeval now; + struct timeval pausedFor; + + evutil_gettimeofday(&now, NULL); + evutil_timersub(&now, &_pausedAt, &pausedFor); + _pausedAt.tv_sec = 0; + _pausedAt.tv_usec = 0; + + for(auto& callbackData : _callbackData) { + // add the time we were paused to all due times + evutil_timeradd(&callbackData.second.due, &pausedFor, &callbackData.second.due); + + struct timeval remain; + evutil_timersub(&callbackData.second.due, &now, &remain); + +#if 0 + std::cout << "Now : " << now.tv_sec << "." << now.tv_usec << std::endl; + std::cout << "Paused : " << pausedFor.tv_sec << "." << pausedFor.tv_usec << std::endl; + std::cout << "Remaining: " << remain.tv_sec << "." << remain.tv_usec << std::endl; +#endif + assert(remain.tv_usec >= 0 && remain.tv_sec >= 0); + + // reenqueue with libevent + event_add(callbackData.second.event, &remain); + } + } + start(); +} + +} diff --git a/contrib/src/uscxml/PausableDelayedEventQueue.h b/contrib/src/uscxml/PausableDelayedEventQueue.h new file mode 100644 index 0000000..ae64e72 --- /dev/null +++ b/contrib/src/uscxml/PausableDelayedEventQueue.h @@ -0,0 +1,53 @@ +/** + * @file + * @author 2017 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#ifndef PAUSABLEDELAYEDEVENTQUEUE_H_F26D6161 +#define PAUSABLEDELAYEDEVENTQUEUE_H_F26D6161 + +#include "uscxml/config.h" +#include "uscxml/Common.h" +#include "uscxml/interpreter/BasicDelayedEventQueue.h" + +#include +#include + +namespace uscxml { + +/** + * A DelayedEventQueue that implements pause/resume + * @ingroup eventqueue + * @ingroup impl + */ +class USCXML_API PausableDelayedEventQueue : public BasicDelayedEventQueue { +public: + PausableDelayedEventQueue(DelayedEventQueueCallbacks* callbacks); + std::shared_ptr create(DelayedEventQueueCallbacks* callbacks); + + void pause(); + void resume(); + +protected: + timeval _pausedAt; +}; + +} + + + +#endif /* end of include guard: PAUSABLEDELAYEDEVENTQUEUE_H_F26D6161 */ diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index 1aa07df..d5f764d 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -187,10 +187,25 @@ void Interpreter::reset() { return _impl->reset(); } +void Interpreter::deserialize(const std::string& encodedState) { + return _impl->deserialize(encodedState); +} + +std::string Interpreter::serialize() { + return _impl->serialize(); +} + InterpreterState Interpreter::step(size_t blockMs) { return _impl->step(blockMs); }; +void loadState(const std::string& encodedState); + +/** + * Save the interpreter's state. + */ +std::string saveState(); + void Interpreter::cancel() { return _impl->cancel(); } @@ -215,6 +230,10 @@ void Interpreter::setActionLanguage(ActionLanguage actionLanguage) { return _impl->setActionLanguage(actionLanguage); } +ActionLanguage Interpreter::getActionLanguage() { + return _impl->getActionLanguage(); +} + void Interpreter::setFactory(Factory* factory) { return _impl->setFactory(factory); } @@ -235,9 +254,7 @@ std::list Interpreter::validate() { return InterpreterIssue::forInterpreter(_impl.get()); } -std::recursive_mutex StateTransitionMonitor::_mutex; - -#if 0 +#if 1 static void printNodeSet(const std::list nodes) { std::string seperator; for (auto nIter = nodes.begin(); nIter != nodes.end(); nIter++) { @@ -247,6 +264,8 @@ static void printNodeSet(const std::list nodes) { } #endif +std::recursive_mutex StateTransitionMonitor::_mutex; + void StateTransitionMonitor::beforeTakingTransition(Interpreter& interpreter, const XERCESC_NS::DOMElement* transition) { std::lock_guard lock(_mutex); std::cerr << "Transition: " << uscxml::DOMUtils::xPathForNode(transition) << std::endl; @@ -255,7 +274,7 @@ void StateTransitionMonitor::beforeTakingTransition(Interpreter& interpreter, co void StateTransitionMonitor::onStableConfiguration(Interpreter& interpreter) { std::lock_guard lock(_mutex); std::cerr << "Stable Config: { "; -// printNodeSet(_interpreter.getConfiguration()); + printNodeSet(interpreter.getConfiguration()); std::cerr << " }" << std::endl; } @@ -292,8 +311,8 @@ void StateTransitionMonitor::beforeEnteringState(Interpreter& interpreter, const void StateTransitionMonitor::beforeMicroStep(Interpreter& interpreter) { std::lock_guard lock(_mutex); - std::cerr << "Config: {"; -// printNodeSet(_interpreter.getConfiguration()); + std::cerr << "Microstep in config: {"; + printNodeSet(interpreter.getConfiguration()); std::cerr << "}" << std::endl; } diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h index cf87fc4..c74736e 100644 --- a/src/uscxml/Interpreter.h +++ b/src/uscxml/Interpreter.h @@ -146,6 +146,16 @@ public: void reset(); /** + * Deserialize the state for the interpreter from a string. + */ + void deserialize(const std::string& encodedState); + + /** + * Serialize the interpreter's state in a string. + */ + std::string serialize(); + + /** * Get all state elements that constitute the active configuration. * @return A list of XML elements of the active states. */ @@ -182,6 +192,11 @@ public: void setActionLanguage(ActionLanguage actionLanguage); /** + * Return ActionLanguage with the instances actually used (i.e. those from the factory). + */ + ActionLanguage getActionLanguage(); + + /** * Provide a custom Factory to instantiate dynamic instances for this and invoked state-chart instances. */ void setFactory(Factory* factory); diff --git a/src/uscxml/interpreter/BasicContentExecutor.cpp b/src/uscxml/interpreter/BasicContentExecutor.cpp index 60a45e5..bbd6bca 100644 --- a/src/uscxml/interpreter/BasicContentExecutor.cpp +++ b/src/uscxml/interpreter/BasicContentExecutor.cpp @@ -438,9 +438,10 @@ void BasicContentExecutor::invoke(XERCESC_NS::DOMElement* element) { // content std::list contents = DOMUtils::filterChildElements(XML_PREFIX(element).str() + "content", element); if (contents.size() > 0) { -#if 1 +#if 0 invokeEvent.data.node = contents.front(); #else + // test530 Data d = elementAsData(contents.front()); if (d.type == Data::INTERPRETED && d.atom.size() > 0) { // immediately evaluate! @@ -558,7 +559,7 @@ void BasicContentExecutor::processParams(std::multimap& param } } -Data BasicContentExecutor::elementAsData(XERCESC_NS::DOMElement* element) { +Data BasicContentExecutor::elementAsData(XERCESC_NS::DOMElement* element, bool asExpression) { if (HAS_ATTR(element, "expr")) { // return _callbacks->evalAsData(ATTR(element, "expr")); #if 0 @@ -570,6 +571,8 @@ Data BasicContentExecutor::elementAsData(XERCESC_NS::DOMElement* element) { return Data(ATTR(element, "expr"), Data::INTERPRETED); } #endif + if (asExpression) // test 453 + return Data(ATTR(element, "expr"), Data::INTERPRETED); return _callbacks->evalAsData(ATTR(element, "expr")); } @@ -587,6 +590,7 @@ Data BasicContentExecutor::elementAsData(XERCESC_NS::DOMElement* element) { // make an attempt to parse as XML try { +#if 0 XERCESC_NS::XercesDOMParser parser; parser.setValidationScheme(XERCESC_NS::XercesDOMParser::Val_Never); parser.setDoNamespaces(true); @@ -604,8 +608,46 @@ Data BasicContentExecutor::elementAsData(XERCESC_NS::DOMElement* element) { XERCESC_NS::DOMDocument* doc = parser.adoptDocument(); d.adoptedDoc = std::shared_ptr(doc); d.node = doc->getDocumentElement(); - return d; +#else + std::unique_ptr parser(new XERCESC_NS::XercesDOMParser()); + parser->setValidationScheme(XERCESC_NS::XercesDOMParser::Val_Always); + parser->setDoNamespaces(true); + parser->useScanner(XERCESC_NS::XMLUni::fgWFXMLScanner); + + std::unique_ptr errHandler(new XERCESC_NS::HandlerBase()); + parser->setErrorHandler(errHandler.get()); + + try { + std::string tmp = url; + parser->parse(tmp.c_str()); + + XERCESC_NS::DOMNode* newNode = element->getOwnerDocument()->importNode(parser->getDocument()->getDocumentElement(), true); + + // remove any old child elements + while(element->getFirstElementChild() != NULL) { + element->removeChild(element->getFirstElementChild()); + } + // we need to save the DOM somewhere .. Data::adoptedDoc was not good enough + element->appendChild(newNode); + + Data d; +// d.adoptedDoc = std::shared_ptr(parser->adoptDocument()); + d.node = newNode; + return d; + } + + catch (const XERCESC_NS::SAXParseException& toCatch) { + ERROR_PLATFORM_THROW(X(toCatch.getMessage()).str()); + } catch (const XERCESC_NS::RuntimeException& toCatch) { + ERROR_PLATFORM_THROW(X(toCatch.getMessage()).str()); + } catch (const XERCESC_NS::XMLException& toCatch) { + ERROR_PLATFORM_THROW(X(toCatch.getMessage()).str()); + } catch (const XERCESC_NS::DOMException& toCatch) { + ERROR_PLATFORM_THROW(X(toCatch.getMessage()).str()); + } + +#endif } catch (...) { // just ignore and return as an interpreted string below @@ -622,10 +664,9 @@ Data BasicContentExecutor::elementAsData(XERCESC_NS::DOMElement* element) { // local content in document std::list elementChildren = DOMUtils::filterChildType(DOMNode::ELEMENT_NODE, element); - if (elementChildren.size() == 1) { - return Data(elementChildren.front()); - } else if (elementChildren.size() > 1) { - return Data(element); + if (elementChildren.size() > 0) { + // always return parent element, even with a single child node + return Data(static_cast(element)); } std::list textChildren = DOMUtils::filterChildType(DOMNode::TEXT_NODE, element); @@ -634,14 +675,23 @@ Data BasicContentExecutor::elementAsData(XERCESC_NS::DOMElement* element) { for (auto textIter = textChildren.begin(); textIter != textChildren.end(); textIter++) { contentSS << X((*textIter)->getNodeValue()); } -#if 0 + + // test294, test562 + if (LOCALNAME(element) == "content") { + return Data(spaceNormalize(contentSS.str()), Data::VERBATIM); + } + + if (asExpression) // not actually used, but likely expected + return Data(contentSS.str(), Data::INTERPRETED); + + // test153 try { Data d = _callbacks->getAsData(contentSS.str()); if (!d.empty()) return d; } catch(...) {} -#endif - // test294, test562 + + // never actually occurs with the w3c tests return Data(spaceNormalize(contentSS.str()), Data::VERBATIM); } } diff --git a/src/uscxml/interpreter/BasicContentExecutor.h b/src/uscxml/interpreter/BasicContentExecutor.h index db53eb2..032da6f 100644 --- a/src/uscxml/interpreter/BasicContentExecutor.h +++ b/src/uscxml/interpreter/BasicContentExecutor.h @@ -52,7 +52,7 @@ public: virtual void uninvoke(XERCESC_NS::DOMElement* invoke); virtual void raiseDoneEvent(XERCESC_NS::DOMElement* state, XERCESC_NS::DOMElement* doneData); - virtual Data elementAsData(XERCESC_NS::DOMElement* element); + virtual Data elementAsData(XERCESC_NS::DOMElement* element, bool asExpression = false); protected: void processNameLists(std::map& nameMap, XERCESC_NS::DOMElement* element); diff --git a/src/uscxml/interpreter/BasicDelayedEventQueue.cpp b/src/uscxml/interpreter/BasicDelayedEventQueue.cpp index 49ed2f9..3c87ac0 100644 --- a/src/uscxml/interpreter/BasicDelayedEventQueue.cpp +++ b/src/uscxml/interpreter/BasicDelayedEventQueue.cpp @@ -170,4 +170,53 @@ void BasicDelayedEventQueue::reset() { _queue.clear(); } +Data BasicDelayedEventQueue::serialize() { + std::lock_guard lock(_mutex); + + if (_isStarted) { + _isStarted = false; + event_base_loopbreak(_eventLoop); + } + if (_thread) { + _thread->join(); + delete _thread; + _thread = NULL; + } + + Data serialized; + + for (auto event : _queue) { + struct callbackData cb = _callbackData[event.uuid]; + + struct timeval delay, now; + uint64_t delayMs = 0; + gettimeofday(&now, NULL); + + evutil_timersub(&delay, &cb.due, &now); + if (delay.tv_sec > 0 || (delay.tv_sec == 0 && delay.tv_usec > 0)) { + delayMs = delay.tv_sec * 1000 + delay.tv_usec / (double)1000; + } + + Data delayedEvent; + delayedEvent["event"] = event; + delayedEvent["delay"] = Data(delayMs, Data::INTERPRETED); + + serialized["BasicDelayedEventQueue"].array.push_back(event); + } + + start(); + return serialized; +} + +void BasicDelayedEventQueue::deserialize(const Data& data) { + if (data.hasKey("BasicDelayedEventQueue")) { + std::lock_guard lock(_mutex); + for (auto event : data["BasicDelayedEventQueue"].array) { + Event e = Event::fromData(event["event"]); + enqueueDelayed(e, strTo(event["delay"]), e.uuid); + } + } + +} + } diff --git a/src/uscxml/interpreter/BasicDelayedEventQueue.h b/src/uscxml/interpreter/BasicDelayedEventQueue.h index df5b13f..dfcd002 100644 --- a/src/uscxml/interpreter/BasicDelayedEventQueue.h +++ b/src/uscxml/interpreter/BasicDelayedEventQueue.h @@ -52,6 +52,9 @@ public: } virtual void reset(); + virtual Data serialize(); + virtual void deserialize(const Data& data); + protected: virtual std::shared_ptr create() { ErrorEvent e("Cannot create a DelayedEventQueue without callbacks"); diff --git a/src/uscxml/interpreter/BasicEventQueue.cpp b/src/uscxml/interpreter/BasicEventQueue.cpp index 519754e..bb7c78b 100644 --- a/src/uscxml/interpreter/BasicEventQueue.cpp +++ b/src/uscxml/interpreter/BasicEventQueue.cpp @@ -73,6 +73,25 @@ void BasicEventQueue::reset() { _queue.clear(); } +Data BasicEventQueue::serialize() { + std::lock_guard lock(_mutex); + Data serialized; + + for (auto event : _queue) { + serialized["BasicEventQueue"].array.push_back(event); + } + return serialized; +} + +void BasicEventQueue::deserialize(const Data& data) { + if (data.hasKey("BasicEventQueue")) { + std::lock_guard lock(_mutex); + for (auto event : data["BasicEventQueue"].array) { + _queue.push_back(Event::fromData(event)); + } + } +} + std::shared_ptr BasicEventQueue::create() { return std::shared_ptr(new BasicEventQueue()); } diff --git a/src/uscxml/interpreter/BasicEventQueue.h b/src/uscxml/interpreter/BasicEventQueue.h index 927b9c4..db74825 100644 --- a/src/uscxml/interpreter/BasicEventQueue.h +++ b/src/uscxml/interpreter/BasicEventQueue.h @@ -42,6 +42,8 @@ public: virtual Event dequeue(size_t blockMs); virtual void enqueue(const Event& event); virtual void reset(); + virtual Data serialize(); + virtual void deserialize(const Data& data); protected: std::list _queue; diff --git a/src/uscxml/interpreter/ContentExecutor.cpp b/src/uscxml/interpreter/ContentExecutor.cpp index ca9d877..de142a1 100644 --- a/src/uscxml/interpreter/ContentExecutor.cpp +++ b/src/uscxml/interpreter/ContentExecutor.cpp @@ -35,8 +35,8 @@ void ContentExecutor::uninvoke(XERCESC_NS::DOMElement* invoke) { _impl->uninvoke(invoke); } -Data ContentExecutor::elementAsData(XERCESC_NS::DOMElement* element) { - return _impl->elementAsData(element); +Data ContentExecutor::elementAsData(XERCESC_NS::DOMElement* element, bool asExpression) { + return _impl->elementAsData(element, asExpression); } void ContentExecutor::raiseDoneEvent(XERCESC_NS::DOMElement* state, XERCESC_NS::DOMElement* doneData) { diff --git a/src/uscxml/interpreter/ContentExecutor.h b/src/uscxml/interpreter/ContentExecutor.h index 1559c5c..7a0b125 100644 --- a/src/uscxml/interpreter/ContentExecutor.h +++ b/src/uscxml/interpreter/ContentExecutor.h @@ -46,7 +46,7 @@ public: virtual void process(XERCESC_NS::DOMElement* block, const X& xmlPrefix); virtual void invoke(XERCESC_NS::DOMElement* invoke); virtual void uninvoke(XERCESC_NS::DOMElement* invoke); - virtual Data elementAsData(XERCESC_NS::DOMElement* element); + virtual Data elementAsData(XERCESC_NS::DOMElement* element, bool asExpression = false); virtual void raiseDoneEvent(XERCESC_NS::DOMElement* state, XERCESC_NS::DOMElement* doneData); virtual std::shared_ptr getImpl() const; diff --git a/src/uscxml/interpreter/ContentExecutorImpl.h b/src/uscxml/interpreter/ContentExecutorImpl.h index dfd4c5f..278cbb9 100644 --- a/src/uscxml/interpreter/ContentExecutorImpl.h +++ b/src/uscxml/interpreter/ContentExecutorImpl.h @@ -94,7 +94,7 @@ public: virtual void uninvoke(XERCESC_NS::DOMElement* invoke) = 0; virtual void raiseDoneEvent(XERCESC_NS::DOMElement* state, XERCESC_NS::DOMElement* doneData) = 0; - virtual Data elementAsData(XERCESC_NS::DOMElement* element) = 0; + virtual Data elementAsData(XERCESC_NS::DOMElement* element, bool asExpression = false) = 0; protected: ContentExecutorCallbacks* _callbacks; diff --git a/src/uscxml/interpreter/EventQueue.cpp b/src/uscxml/interpreter/EventQueue.cpp index 9b345d5..fa9c4c1 100644 --- a/src/uscxml/interpreter/EventQueue.cpp +++ b/src/uscxml/interpreter/EventQueue.cpp @@ -42,6 +42,14 @@ void EventQueue::reset() { return _impl->reset(); } +Data EventQueue::serialize() { + return _impl->serialize(); +} + +void EventQueue::deserialize(const Data& data) { + return _impl->deserialize(data); +} + std::shared_ptr EventQueue::getImplBase() { return _impl; } diff --git a/src/uscxml/interpreter/EventQueue.h b/src/uscxml/interpreter/EventQueue.h index 5ca1f52..76fdaa2 100644 --- a/src/uscxml/interpreter/EventQueue.h +++ b/src/uscxml/interpreter/EventQueue.h @@ -39,6 +39,10 @@ public: virtual Event dequeue(size_t blockMs); virtual void enqueue(const Event& event); virtual void reset(); + + Data serialize(); + void deserialize(const Data& data); + virtual std::shared_ptr getImplBase(); protected: diff --git a/src/uscxml/interpreter/EventQueueImpl.h b/src/uscxml/interpreter/EventQueueImpl.h index 2a33a75..50ac608 100644 --- a/src/uscxml/interpreter/EventQueueImpl.h +++ b/src/uscxml/interpreter/EventQueueImpl.h @@ -42,6 +42,8 @@ public: virtual Event dequeue(size_t blockMs) = 0; virtual void enqueue(const Event& event) = 0; virtual void reset() = 0; + virtual Data serialize() = 0; + virtual void deserialize(const Data& data) = 0; }; /** @@ -63,6 +65,10 @@ public: virtual void enqueueDelayed(const Event& event, size_t delayMs, const std::string& eventUUID) = 0; virtual void cancelDelayed(const std::string& eventId) = 0; virtual void cancelAllDelayed() = 0; + + virtual Data serialize() = 0; + virtual void deserialize(const Data& data) = 0; + }; } diff --git a/src/uscxml/interpreter/FastMicroStep.cpp b/src/uscxml/interpreter/FastMicroStep.cpp index 149ccb4..bf9749d 100644 --- a/src/uscxml/interpreter/FastMicroStep.cpp +++ b/src/uscxml/interpreter/FastMicroStep.cpp @@ -94,6 +94,45 @@ std::shared_ptr FastMicroStep::create(MicroStepCallbacks* callbac return std::shared_ptr(new FastMicroStep(callbacks)); } +void FastMicroStep::deserialize(const Data& encodedState) { + if (!encodedState.hasKey("configuration") || + !encodedState.hasKey("invocations") || + !encodedState.hasKey("histories") || + !encodedState.hasKey("intializedData")) { + ERROR_PLATFORM_THROW("Data does not contain required fields for deserialization "); + } + + _configuration = fromBase64(encodedState["configuration"].atom); + assert(_configuration.size() > 0 && _configuration.num_blocks() > 0); + _invocations = fromBase64(encodedState["invocations"].atom); + _history = fromBase64(encodedState["histories"].atom); + _initializedData = fromBase64(encodedState["intializedData"].atom); + + for (size_t i = 0; i < USCXML_NUMBER_STATES; i++) { + if (BIT_HAS(i, _invocations) && USCXML_GET_STATE(i).invoke.size() > 0) { + for (auto invIter = USCXML_GET_STATE(i).invoke.begin(); invIter != USCXML_GET_STATE(i).invoke.end(); invIter++) { + try { + _callbacks->invoke(*invIter); + } catch (ErrorEvent e) { + LOG(_callbacks->getLogger(), USCXML_WARN) << e; + } catch (...) { + } + } + } + } + + _flags |= USCXML_CTX_INITIALIZED; +} + +Data FastMicroStep::serialize() { + Data encodedState; + encodedState["configuration"] = Data(toBase64(_configuration)); + encodedState["invocations"] = Data(toBase64(_invocations)); + encodedState["histories"] = Data(toBase64(_history)); + encodedState["intializedData"] = Data(toBase64(_initializedData)); + return encodedState; +} + void FastMicroStep::resortStates(DOMElement* element, const X& xmlPrefix) { /** @@ -197,11 +236,11 @@ void FastMicroStep::init(XERCESC_NS::DOMElement* scxml) { _xmlPrefix = std::string(_xmlPrefix) + ":"; } - resortStates(_scxml, _xmlPrefix); + resortStates(_scxml, _xmlPrefix); #ifdef WITH_CACHE_FILES bool withCache = !envVarIsTrue("USCXML_NOCACHE_FILES"); - Data& cache = _callbacks->getCache().compound["FastMicroStep"]; + Data& cache = _callbacks->getCache().compound["FastMicroStep"]; #endif /** -- All things states -- */ @@ -319,10 +358,17 @@ void FastMicroStep::init(XERCESC_NS::DOMElement* scxml) { // establish the states' completion #ifdef WITH_CACHE_FILES if (withCache && cachedState->compound.find("completion") != cachedState->compound.end()) { - _states[i]->completion = fromBase64(cachedState->compound["completion"]); - } else { + boost::dynamic_bitset completion = fromBase64(cachedState->compound["completion"]); + if (completion.size() != _states.size()) { + LOG(_callbacks->getLogger(), USCXML_WARN) << "State completion has wrong size: Cache corrupted"; + } else { + _states[i]->completion = completion; + goto COMPLETION_STABLISHED; + } + } #endif - std::list completion = getCompletion(_states[i]->element); + { + std::list completion = getCompletion(_states[i]->element); for (j = 0; j < _states.size(); j++) { if (!completion.empty() && _states[j]->element == completion.front()) { _states[i]->completion[j] = true; @@ -332,12 +378,13 @@ void FastMicroStep::init(XERCESC_NS::DOMElement* scxml) { } } assert(completion.size() == 0); -#ifdef WITH_CACHE_FILES - if (withCache) - cachedState->compound["completion"] = Data(toBase64(_states[i]->completion)); } +#ifdef WITH_CACHE_FILES + if (withCache) + cachedState->compound["completion"] = Data(toBase64(_states[i]->completion)); +COMPLETION_STABLISHED: #endif - // this is set when establishing the completion + // this is set when establishing the completion if (_states[i]->element->getUserData(X("hasHistoryChild")) == _states[i]) { _states[i]->type |= USCXML_STATE_HAS_HISTORY; } @@ -400,16 +447,23 @@ void FastMicroStep::init(XERCESC_NS::DOMElement* scxml) { } } #endif - + // establish the transitions' exit set assert(_transitions[i]->element != NULL); #ifdef WITH_CACHE_FILES if (withCache && cachedTrans->compound.find("exitset") != cachedTrans->compound.end()) { - _transitions[i]->exitSet = fromBase64(cachedTrans->compound["exitset"]); - } else { + boost::dynamic_bitset exitSet = fromBase64(cachedTrans->compound["exitset"]); + if (exitSet.size() != _states.size()) { + LOG(_callbacks->getLogger(), USCXML_WARN) << "Transition exit set has wrong size: Cache corrupted"; + } else { + _transitions[i]->exitSet = exitSet; + goto EXIT_SET_ESTABLISHED; + } + } #endif - std::list exitList = getExitSetCached(_transitions[i]->element, _scxml); + { + std::list exitList = getExitSetCached(_transitions[i]->element, _scxml); for (j = 0; j < _states.size(); j++) { if (!exitList.empty() && _states[j]->element == exitList.front()) { @@ -420,54 +474,67 @@ void FastMicroStep::init(XERCESC_NS::DOMElement* scxml) { } } assert(exitList.size() == 0); - -#ifdef WITH_CACHE_FILES - if (withCache) - cachedTrans->compound["exitset"] = Data(toBase64(_transitions[i]->exitSet)); } +#ifdef WITH_CACHE_FILES + if (withCache) + cachedTrans->compound["exitset"] = Data(toBase64(_transitions[i]->exitSet)); +EXIT_SET_ESTABLISHED: #endif // establish the transitions' conflict set #ifdef WITH_CACHE_FILES if (withCache && cachedTrans->compound.find("conflicts") != cachedTrans->compound.end()) { - _transitions[i]->conflicts = fromBase64(cachedTrans->compound["conflicts"]); - } else { -#endif - for (j = i; j < _transitions.size(); j++) { - if (conflictsCached(_transitions[i]->element, _transitions[j]->element, _scxml)) { - _transitions[i]->conflicts[j] = true; - } else { - _transitions[i]->conflicts[j] = false; - } - // std::cout << "."; + boost::dynamic_bitset conflicts = fromBase64(cachedTrans->compound["conflicts"]); + if (conflicts.size() != _transitions.size()) { + LOG(_callbacks->getLogger(), USCXML_WARN) << "Transition conflicts has wrong size: Cache corrupted"; + } else { + _transitions[i]->conflicts = conflicts; + goto CONFLICTS_ESTABLISHED; } - - // conflicts matrix is symmetric - for (j = 0; j < i; j++) { - _transitions[i]->conflicts[j] = _transitions[j]->conflicts[i]; + } +#endif + for (j = i; j < _transitions.size(); j++) { + if (conflictsCached(_transitions[i]->element, _transitions[j]->element, _scxml)) { + _transitions[i]->conflicts[j] = true; + } else { + _transitions[i]->conflicts[j] = false; } -#ifdef WITH_CACHE_FILES - if (withCache) - cachedTrans->compound["conflicts"] = Data(toBase64(_transitions[i]->conflicts)); + // std::cout << "."; + } + // conflicts matrix is symmetric + for (j = 0; j < i; j++) { + _transitions[i]->conflicts[j] = _transitions[j]->conflicts[i]; } +#ifdef WITH_CACHE_FILES + if (withCache) + cachedTrans->compound["conflicts"] = Data(toBase64(_transitions[i]->conflicts)); +CONFLICTS_ESTABLISHED: #endif // establish the transitions' target set #ifdef WITH_CACHE_FILES if (withCache && cachedTrans->compound.find("target") != cachedTrans->compound.end()) { - _transitions[i]->target = fromBase64(cachedTrans->compound["target"]); - } else { + boost::dynamic_bitset target = fromBase64(cachedTrans->compound["target"]); + if (target.size() != _states.size()) { + LOG(_callbacks->getLogger(), USCXML_WARN) << "Transition target set has wrong size: Cache corrupted"; + } else { + _transitions[i]->target = target; + goto TARGET_SET_ESTABLISHED; + } + } #endif - std::list targets = tokenize(ATTR(_transitions[i]->element, "target")); + { + std::list targets = tokenize(ATTR(_transitions[i]->element, "target")); for (auto tIter = targets.begin(); tIter != targets.end(); tIter++) { if (_stateIds.find(*tIter) != _stateIds.end()) { _transitions[i]->target[_stateIds[*tIter]] = true; } } -#ifdef WITH_CACHE_FILES - if (withCache) - cachedTrans->compound["target"] = Data(toBase64(_transitions[i]->target)); } +#ifdef WITH_CACHE_FILES + if (withCache) + cachedTrans->compound["target"] = Data(toBase64(_transitions[i]->target)); +TARGET_SET_ESTABLISHED: #endif // the transition's source State* uscxmlState = (State*)(_transitions[i]->element->getParentNode()->getUserData(X("uscxmlState"))); @@ -656,6 +723,7 @@ InterpreterState FastMicroStep::step(size_t blockMs) { USCXML_MONITOR_CALLBACK(_callbacks->getMonitors(), onStableConfiguration); _microstepConfigurations.clear(); _flags |= USCXML_CTX_STABLE; + return USCXML_MACROSTEPPED; } if ((_event = _callbacks->dequeueExternal(blockMs))) { @@ -726,9 +794,9 @@ SELECT_TRANSITIONS: _flags |= USCXML_CTX_SPONTANEOUS; _flags &= ~USCXML_CTX_TRANSITION_FOUND; } else { - // spontaneuous transitions are exhausted + // spontaneuous transitions are exhausted and we will attempt to dequeue an internal event next round _flags &= ~USCXML_CTX_SPONTANEOUS; - return USCXML_MACROSTEPPED; + return USCXML_MICROSTEPPED; } USCXML_MONITOR_CALLBACK(_callbacks->getMonitors(), beforeMicroStep); diff --git a/src/uscxml/interpreter/FastMicroStep.h b/src/uscxml/interpreter/FastMicroStep.h index db3e724..97b7eeb 100644 --- a/src/uscxml/interpreter/FastMicroStep.h +++ b/src/uscxml/interpreter/FastMicroStep.h @@ -57,6 +57,9 @@ public: virtual std::list getConfiguration(); void markAsCancelled(); + virtual void deserialize(const Data& encodedState); + virtual Data serialize(); + protected: class Transition { public: diff --git a/src/uscxml/interpreter/InterpreterImpl.cpp b/src/uscxml/interpreter/InterpreterImpl.cpp index f9da040..60ce29d 100644 --- a/src/uscxml/interpreter/InterpreterImpl.cpp +++ b/src/uscxml/interpreter/InterpreterImpl.cpp @@ -111,7 +111,7 @@ InterpreterImpl::~InterpreterImpl() { // ::xercesc_3_1::XMLPlatformUtils::Terminate(); #ifdef WITH_CACHE_FILES - if (!envVarIsTrue("USCXML_NOCACHE_FILES")) { + if (!envVarIsTrue("USCXML_NOCACHE_FILES") && _document != NULL) { // save our cache std::string sharedTemp = URL::getTempDir(true); std::ofstream dataFS(sharedTemp + PATH_SEPERATOR + md5(_baseURL) + ".uscxml.cache"); @@ -123,12 +123,139 @@ InterpreterImpl::~InterpreterImpl() { #endif } +InterpreterState InterpreterImpl::step(size_t blockMs) { + std::lock_guard lock(_serializationMutex); + if (!_isInitialized) { + init(); + _state = USCXML_INITIALIZED; + } else { + _state = _microStepper.step(blockMs); + } + return _state; +} + +void InterpreterImpl::reset() { + if (_microStepper) + _microStepper.reset(); + + _isInitialized = false; + _state = USCXML_INSTANTIATED; + // _dataModel.reset(); + if (_delayQueue) + _delayQueue.reset(); + // _contentExecutor.reset(); +} + void InterpreterImpl::cancel() { + _microStepper.markAsCancelled(); Event unblock; enqueueExternal(unblock); } +void InterpreterImpl::deserialize(const std::string& encodedState) { + + init(); +// std::cout << encodedState << std::endl; + Data state = Data::fromJSON(encodedState); + if (!state.hasKey("microstepper")) { + ERROR_PLATFORM_THROW("No microstepper information in serialized state"); + } + + if (!state.hasKey("url")) { + ERROR_PLATFORM_THROW("No url information in serialized state"); + } + + if (!state.hasKey("md5")) { + ERROR_PLATFORM_THROW("No MD5 hash in serialized state"); + } + + if (state.hasKey("externalQueue")) { + _externalQueue.deserialize(state["externalQueue"]); + } + + if (state.hasKey("delayQueue")) { + _delayQueue.deserialize(state["delayQueue"]); + } + + if (_md5.size() == 0) { + // get md5 of current document + std::stringstream ss; + ss << *_document; + _md5 = md5(ss.str()); + } + + if (state["md5"].atom != _md5) { + ERROR_PLATFORM_THROW("MD5 hash mismatch in serialized state"); + } + + std::list datas = DOMUtils::inDocumentOrder({ XML_PREFIX(_scxml).str() + "data" }, _scxml); + for (auto data : datas) { + if (HAS_ATTR(data, "id") && state["datamodel"].hasKey(ATTR(data, "id"))) + _dataModel.init(ATTR(data, "id"), state["datamodel"][ATTR(data, "id")]); + } + + _microStepper.deserialize(state["microstepper"]); + + std::list invokes = DOMUtils::inDocumentOrder({ XML_PREFIX(_scxml).str() + "invoke" }, _scxml); + for (auto invokeElem : invokes) { + // BasicContentExecutor sets invokeid userdata upon invocation + char* invokeId = (char*)invokeElem->getUserData(X("invokeid")); + if (invokeId != NULL && _invokers.find(invokeId) != _invokers.end()) { + std::string path = DOMUtils::xPathForNode(invokeElem); + if (state.hasKey("invoker") && state["invoker"].hasKey(path)) { + _invokers[invokeId].deserialize(state["invoker"][path]); + } + } + } + +} + +std::string InterpreterImpl::serialize() { + std::lock_guard lock(_serializationMutex); + + Data serialized; + if (_state != USCXML_IDLE && _state != USCXML_MACROSTEPPED && _state != USCXML_FINISHED) { + ERROR_PLATFORM_THROW("Cannot serialize an unstable interpreter"); + } + + if (_md5.size() == 0) { + // get md5 of current document + std::stringstream ss; + ss << *_document; + _md5 = md5(ss.str()); + } + + serialized["md5"] = Data(_md5); + serialized["url"] = Data(std::string(_baseURL)); + serialized["microstepper"] = _microStepper.serialize(); + + // SCXML Rec: "the values of all attributes of type "id" must be unique within the session" + std::list datas = DOMUtils::inDocumentOrder({ XML_PREFIX(_scxml).str() + "data" }, _scxml); + for (auto data : datas) { + if (HAS_ATTR(data, "id")) { + serialized["datamodel"][ATTR(data, "id")] = _dataModel.evalAsData(ATTR(data, "id")); + } + } + + // save all invokers' state + std::list invokes = DOMUtils::inDocumentOrder({ XML_PREFIX(_scxml).str() + "invoke" }, _scxml); + for (auto invokeElem : invokes) { + // BasicContentExecutor sets invokeid userdata upon invocation + char* invokeId = (char*)invokeElem->getUserData(X("invokeid")); + if (invokeId != NULL && _invokers.find(invokeId) != _invokers.end()) { + std::string path = DOMUtils::xPathForNode(invokeElem); + serialized["invoker"][path] = _invokers[invokeId].serialize(); + } + } + +// serialized["internalQueue"] = _internalQueue.serialize(); + serialized["externalQueue"] = _externalQueue.serialize(); + serialized["delayQueue"] = _delayQueue.serialize(); + + return serialized.asJSON(); +} + void InterpreterImpl::setupDOM() { @@ -209,7 +336,7 @@ void InterpreterImpl::init() { _cache.compound["InterpreterImpl"].compound["md5"] = Data(_md5); } #endif - + // register io processors std::map ioProcs = _factory->getIOProcessors(); for (auto ioProcIter = ioProcs.begin(); ioProcIter != ioProcs.end(); ioProcIter++) { @@ -231,7 +358,6 @@ void InterpreterImpl::init() { _ioProcs[*nameIter] = _ioProcs[ioProcIter->first]; } } - } if (!_microStepper) { @@ -268,7 +394,13 @@ void InterpreterImpl::initData(XERCESC_NS::DOMElement* root) { } else if (_invokeReq.namelist.find(id) != _invokeReq.namelist.end()) { _dataModel.init(id, _invokeReq.namelist[id]); } else { - _dataModel.init(id, _execContent.elementAsData(root)); + try { + _dataModel.init(id, _execContent.elementAsData(root)); + } catch (ErrorEvent e) { + // test 453 + _dataModel.init(id, _execContent.elementAsData(root, true)); + + } } } catch(ErrorEvent e) { // test 277 diff --git a/src/uscxml/interpreter/InterpreterImpl.h b/src/uscxml/interpreter/InterpreterImpl.h index 0efc70a..2b12624 100644 --- a/src/uscxml/interpreter/InterpreterImpl.h +++ b/src/uscxml/interpreter/InterpreterImpl.h @@ -64,30 +64,14 @@ public: void cloneFrom(InterpreterImpl* other); void cloneFrom(std::shared_ptr other); - virtual InterpreterState step(size_t blockMs) { - if (!_isInitialized) { - init(); - _state = USCXML_INITIALIZED; - } else { - _state = _microStepper.step(blockMs); - } - return _state; - } - - virtual void reset() {///< Reset state machine - if (_microStepper) - _microStepper.reset(); - - _isInitialized = false; - _state = USCXML_INSTANTIATED; -// _dataModel.reset(); - if (_delayQueue) - _delayQueue.reset(); -// _contentExecutor.reset(); - } + virtual InterpreterState step(size_t blockMs); + virtual void reset();///< Reset state machine virtual void cancel(); ///< Cancel and finalize state machine + virtual void deserialize(const std::string& encodedState); + virtual std::string serialize(); + InterpreterState getState() { return _state; } @@ -240,6 +224,18 @@ public: _delayQueue = al.delayedQueue; } + ActionLanguage getActionLanguage() { + ActionLanguage al; + al.logger = _logger; + al.execContent = _execContent; + al.microStepper = _microStepper; + al.dataModel = _dataModel; + al.internalQueue = _internalQueue; + al.externalQueue = _externalQueue; + al.delayedQueue = _delayQueue; + return al; + } + void setFactory(Factory* factory) { _factory = factory; } @@ -274,6 +270,7 @@ protected: static std::map > _instances; static std::recursive_mutex _instanceMutex; std::recursive_mutex _delayMutex; + std::recursive_mutex _serializationMutex; friend class Interpreter; friend class InterpreterIssue; diff --git a/src/uscxml/interpreter/InterpreterMonitor.h b/src/uscxml/interpreter/InterpreterMonitor.h index a3c527d..7207558 100644 --- a/src/uscxml/interpreter/InterpreterMonitor.h +++ b/src/uscxml/interpreter/InterpreterMonitor.h @@ -102,7 +102,7 @@ protected: class USCXML_API StateTransitionMonitor : public uscxml::InterpreterMonitor { public: - StateTransitionMonitor() {} + StateTransitionMonitor(std::string prefix = "") : _logPrefix(prefix) {} virtual ~StateTransitionMonitor() {} virtual void beforeTakingTransition(Interpreter& interpreter, const XERCESC_NS::DOMElement* transition); @@ -115,6 +115,7 @@ public: protected: static std::recursive_mutex _mutex; + std::string _logPrefix; }; } diff --git a/src/uscxml/interpreter/MicroStep.cpp b/src/uscxml/interpreter/MicroStep.cpp index 896c92a..e5de1dc 100644 --- a/src/uscxml/interpreter/MicroStep.cpp +++ b/src/uscxml/interpreter/MicroStep.cpp @@ -44,6 +44,14 @@ void MicroStep::markAsCancelled() { _impl->markAsCancelled(); } +void MicroStep::deserialize(const Data& encodedState) { + _impl->deserialize(encodedState); +} + +Data MicroStep::serialize() { + return _impl->serialize(); +} + std::shared_ptr MicroStep::getImpl() const { return _impl; } diff --git a/src/uscxml/interpreter/MicroStep.h b/src/uscxml/interpreter/MicroStep.h index 56a7ee8..bda8fd7 100644 --- a/src/uscxml/interpreter/MicroStep.h +++ b/src/uscxml/interpreter/MicroStep.h @@ -26,6 +26,7 @@ #include #include "uscxml/Common.h" +#include "uscxml/messages/Data.h" #include "uscxml/interpreter/InterpreterState.h" @@ -55,6 +56,12 @@ public: virtual void init(XERCESC_NS::DOMElement* scxml); virtual void markAsCancelled(); + /// @copydoc MicroStepImpl::deserialize + virtual void deserialize(const Data& encodedState); + + /// @copydoc MicroStepImpl::serialize + virtual Data serialize(); + std::shared_ptr getImpl() const; protected: std::shared_ptr _impl; diff --git a/src/uscxml/interpreter/MicroStepImpl.h b/src/uscxml/interpreter/MicroStepImpl.h index e3f8299..7ff9469 100644 --- a/src/uscxml/interpreter/MicroStepImpl.h +++ b/src/uscxml/interpreter/MicroStepImpl.h @@ -61,8 +61,9 @@ public: virtual Interpreter getInterpreter() = 0; virtual Logger getLogger() = 0; - /** Saved State */ + /** Cache Data */ virtual Data& getCache() = 0; + }; /** @@ -87,6 +88,9 @@ public: virtual void init(XERCESC_NS::DOMElement* scxml) = 0; virtual void markAsCancelled() = 0; + virtual void deserialize(const Data& encodedState) = 0; + virtual Data serialize() = 0; + protected: MicroStepCallbacks* _callbacks; diff --git a/src/uscxml/messages/Data.cpp b/src/uscxml/messages/Data.cpp index de9644b..277b7d8 100644 --- a/src/uscxml/messages/Data.cpp +++ b/src/uscxml/messages/Data.cpp @@ -101,8 +101,7 @@ Data Data::fromJSON(const std::string& jsonString) { } t = (jsmntok_t*)malloc((nrTokens + 1) * sizeof(jsmntok_t)); if (t == NULL) { - throw ErrorEvent("Cannot parse JSON, ran out of memory!"); - return data; + ERROR_PLATFORM_THROW("Cannot parse JSON, ran out of memory!"); } memset(t, 0, (nrTokens + 1) * sizeof(jsmntok_t)); @@ -111,15 +110,18 @@ Data Data::fromJSON(const std::string& jsonString) { if (rv != 0) { switch (rv) { - case JSMN_ERROR_NOMEM: - throw ErrorEvent("Cannot parse JSON, not enough tokens were provided!"); + case JSMN_ERROR_NOMEM: { + ERROR_PLATFORM_THROW("Cannot parse JSON, not enough tokens were provided!"); break; - case JSMN_ERROR_INVAL: - throw ErrorEvent("Cannot parse JSON, invalid character inside JSON string!"); + } + case JSMN_ERROR_INVAL: { + ERROR_PLATFORM_THROW("Cannot parse JSON, invalid character inside JSON string!"); break; - case JSMN_ERROR_PART: - throw ErrorEvent("Cannot parse JSON, the string is not a full JSON packet, more bytes expected!"); + } + case JSMN_ERROR_PART: { + ERROR_PLATFORM_THROW("Cannot parse JSON, the string is not a full JSON packet, more bytes expected!"); break; + } default: break; } @@ -151,10 +153,11 @@ Data Data::fromJSON(const std::string& jsonString) { dataStack.back()->type = Data::VERBATIM; case JSMN_PRIMITIVE: { std::string value = trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start); - if (dataStack.back()->type == Data::VERBATIM) { - boost::replace_all(value, "\\\"", "\""); - boost::replace_all(value, "\\n", "\n"); - } +// if (dataStack.back()->type == Data::VERBATIM) { +// boost::replace_all(value, "\\\"", "\""); +// boost::replace_all(value, "\\n", "\n"); +// } + value = jsonUnescape(value); dataStack.back()->atom = value; dataStack.pop_back(); currTok++; @@ -182,7 +185,7 @@ Data Data::fromJSON(const std::string& jsonString) { if (tokenStack.back().type == JSMN_OBJECT && (t[currTok].type == JSMN_PRIMITIVE || t[currTok].type == JSMN_STRING)) { // grab key and push new data - std::string value = trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start); + std::string value = jsonUnescape(trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start)); dataStack.push_back(&(dataStack.back()->compound[value])); currTok++; } @@ -230,7 +233,7 @@ std::string Data::toJSON(const Data& data) { os << std::endl << indent << "{"; compoundIter = data.compound.begin(); while(compoundIter != data.compound.end()) { - os << seperator << std::endl << indent << " \"" << compoundIter->first << "\": " << keyPadding.substr(0, longestKey - compoundIter->first.size()); + os << seperator << std::endl << indent << " \"" << jsonEscape(compoundIter->first) << "\": " << keyPadding.substr(0, longestKey - compoundIter->first.size()); _dataIndentation += 1; os << compoundIter->second; _dataIndentation -= 1; @@ -254,38 +257,110 @@ std::string Data::toJSON(const Data& data) { } else if (data.atom.size() > 0) { // empty string is handled below if (data.type == Data::VERBATIM) { - os << "\""; - for (size_t i = 0; i < data.atom.size(); i++) { - // escape string - if (false) { - } else if (data.atom[i] == '"') { - os << "\\\""; - } else if (data.atom[i] == '\n') { - os << "\\n"; - } else if (data.atom[i] == '\t') { - os << "\\t"; - } else { - os << data.atom[i]; - } - } - os << "\""; + os << "\"" << jsonEscape(data.atom) << "\""; } else { os << data.atom; } } else if (data.node) { std::ostringstream xmlSerSS; - xmlSerSS << data.node; + xmlSerSS << *data.node; std::string xmlSer = xmlSerSS.str(); - boost::replace_all(xmlSer, "\"", "\\\""); - boost::replace_all(xmlSer, "\n", "\\n"); - boost::replace_all(xmlSer, "\t", "\\t"); - os << "\"" << xmlSer << "\""; +// boost::replace_all(xmlSer, "\"", "\\\""); +// boost::replace_all(xmlSer, "\n", "\\n"); +// boost::replace_all(xmlSer, "\t", "\\t"); + os << "\"" << jsonEscape(xmlSer) << "\""; } else { if (data.type == Data::VERBATIM) { os << "\"\""; // empty string + } else { + os << "null"; // non object } } return os.str(); } +std::string Data::jsonUnescape(const std::string& expr) { + + // http://stackoverflow.com/a/19636328/990120 + bool escape = false; + std::string output; + output.reserve(expr.length()); + + for (std::string::size_type i = 0; i < expr.length(); ++i) { + if (escape) { + switch(expr[i]) { + case '"': + output += '\"'; + break; + case '/': + output += '/'; + break; + case 'b': + output += '\b'; + break; + case 'f': + output += '\f'; + break; + case 'n': + output += '\n'; + break; + case 'r': + output += '\r'; + break; + case 't': + output += '\t'; + break; + case '\\': + output += '\\'; + break; + default: + output += expr[i]; + break; + } + escape = false; + } else { + switch(expr[i]) { + case '\\': + escape = true; + break; + default: + output += expr[i]; + break; + } + } + } + return output; + +} + +std::string Data::jsonEscape(const std::string& expr) { + std::stringstream os; + for (size_t i = 0; i < expr.size(); i++) { + // escape string + if (false) { + } else if (expr[i] == '\t') { + os << "\\t"; + } else if (expr[i] == '\v') { + os << "\\v"; + } else if (expr[i] == '\b') { + os << "\\b"; + } else if (expr[i] == '\f') { + os << "\\f"; + } else if (expr[i] == '\n') { + os << "\\n"; + } else if (expr[i] == '\r') { + os << "\\r"; + } else if (expr[i] == '\'') { + os << "\\'"; + } else if (expr[i] == '\"') { + os << "\\\""; + } else if (expr[i] == '\\') { + os << "\\\\"; + } else { + os << expr[i]; + } + } + + return os.str(); +} } diff --git a/src/uscxml/messages/Data.h b/src/uscxml/messages/Data.h index 17d37cb..19134dd 100644 --- a/src/uscxml/messages/Data.h +++ b/src/uscxml/messages/Data.h @@ -71,14 +71,15 @@ public: explicit Data(T value, Type type, typename std::enable_if::value>::type* = nullptr) : node(NULL), atom(toStr(value)), type(type) {} - ~Data() {} + ~Data() { + } void clear() { type = VERBATIM; compound.clear(); array.clear(); atom.clear(); - adoptedDoc.reset(); +// adoptedDoc.reset(); binary = Blob(); node = NULL; } @@ -234,7 +235,7 @@ protected: #endif XERCESC_NS::DOMNode* node; - std::shared_ptr adoptedDoc; +// std::shared_ptr adoptedDoc; std::map compound; std::list array; std::string atom; @@ -242,7 +243,10 @@ protected: Type type; protected: + static std::string jsonEscape(const std::string& expr); + static std::string jsonUnescape(const std::string& expr); friend USCXML_API std::ostream& operator<< (std::ostream& os, const Data& data); + }; USCXML_API std::ostream& operator<< (std::ostream& os, const Data& data); diff --git a/src/uscxml/messages/Event.cpp b/src/uscxml/messages/Event.cpp index c4aa642..f372798 100644 --- a/src/uscxml/messages/Event.cpp +++ b/src/uscxml/messages/Event.cpp @@ -22,6 +22,62 @@ namespace uscxml { +Event Event::fromData(const Data& data) { + Event e; + if (data.hasKey("data")) + e.data = Data(data["data"]); + if (data.hasKey("raw")) + e.raw = data["raw"].atom; + if (data.hasKey("name")) + e.name = data["name"].atom; + if (data.hasKey("eventType")) + e.eventType = (Type)strTo(data["eventType"].atom); + if (data.hasKey("origin")) + e.origin = data["origin"].atom; + if (data.hasKey("origintype")) + e.origintype = data["origintype"].atom; + if (data.hasKey("sendid")) + e.sendid = data["sendid"].atom; + if (data.hasKey("hideSendId")) + e.hideSendId = strTo(data["hideSendId"].atom); + if (data.hasKey("invokeid")) + e.invokeid = data["invokeid"].atom; + if (data.hasKey("uuid")) + e.uuid = data["uuid"].atom; + if (data.hasKey("namelist")) + e.namelist = data["namelist"].compound; + + if (data.hasKey("params")) { + for (auto param : data["params"].array) { + e.params.insert(std::make_pair(param.compound.begin()->first, param.compound.begin()->second)); + } + } + return e; +} + +Event::operator Data() { + Data data; + data["data"] = data; + data["raw"] = Data(raw, Data::VERBATIM); + data["name"] = Data(name, Data::VERBATIM); + data["eventType"] = Data(eventType, Data::VERBATIM); + data["origin"] = Data(origin, Data::VERBATIM); + data["origintype"] = Data(origintype, Data::VERBATIM); + data["sendid"] = Data(sendid, Data::VERBATIM); + data["hideSendId"] = Data(hideSendId, Data::VERBATIM); + data["invokeid"] = Data(invokeid, Data::VERBATIM); + data["uuid"] = Data(uuid, Data::VERBATIM); + data["namelist"].compound = namelist; + + for (auto param : params) { + Data entry; + entry.compound[param.first] = param.second; + data["params"].array.push_back(entry); + } + + return data; +} + std::ostream& operator<< (std::ostream& os, const Event& event) { std::string indent; for (size_t i = 0; i < _dataIndentation; i++) { @@ -67,4 +123,4 @@ std::ostream& operator<< (std::ostream& os, const Event& event) { return os; } -} \ No newline at end of file +} diff --git a/src/uscxml/messages/Event.h b/src/uscxml/messages/Event.h index 59b2690..b774f8a 100644 --- a/src/uscxml/messages/Event.h +++ b/src/uscxml/messages/Event.h @@ -24,20 +24,26 @@ #include "uscxml/util/UUID.h" #define ERROR_PLATFORM_THROW(msg) \ - ErrorEvent e; \ + uscxml::ErrorEvent e; \ e.name = "error.platform"; \ - e.data.compound["cause"] = Data(msg, Data::VERBATIM); \ + e.data.compound["cause"] = uscxml::Data(msg, uscxml::Data::VERBATIM); \ + e.data.compound["file"] = uscxml::Data(uscxml::toStr(__FILE__), uscxml::Data::VERBATIM); \ + e.data.compound["line"] = uscxml::Data(uscxml::toStr(__LINE__), uscxml::Data::INTERPRETED); \ throw e; \ #define ERROR_EXECUTION(identifier, cause) \ uscxml::ErrorEvent identifier; \ identifier.data.compound["cause"] = uscxml::Data(cause, uscxml::Data::VERBATIM); \ + identifier.data.compound["file"] = uscxml::Data(uscxml::toStr(__FILE__), uscxml::Data::VERBATIM); \ + identifier.data.compound["line"] = uscxml::Data(uscxml::toStr(__LINE__), uscxml::Data::INTERPRETED); \ identifier.name = "error.execution"; \ identifier.eventType = uscxml::Event::PLATFORM; #define ERROR_EXECUTION2(identifier, cause, node) \ uscxml::ErrorEvent identifier; \ identifier.data.compound["cause"] = uscxml::Data(cause, uscxml::Data::VERBATIM); \ + identifier.data.compound["file"] = uscxml::Data(uscxml::toStr(__FILE__), uscxml::Data::VERBATIM); \ + identifier.data.compound["line"] = uscxml::Data(uscxml::toStr(__LINE__), uscxml::Data::INTERPRETED); \ identifier.name = "error.execution"; \ identifier.data.compound["xpath"] = uscxml::Data(DOMUtils::xPathForNode(node), uscxml::Data::VERBATIM); \ identifier.eventType = uscxml::Event::PLATFORM; @@ -45,12 +51,16 @@ #define ERROR_COMMUNICATION(identifier, cause) \ uscxml::ErrorEvent identifier; \ identifier.data.compound["cause"] = uscxml::Data(cause, uscxml::Data::VERBATIM); \ + identifier.data.compound["file"] = uscxml::Data(uscxml::toStr(__FILE__), uscxml::Data::VERBATIM); \ + identifier.data.compound["line"] = uscxml::Data(uscxml::toStr(__LINE__), uscxml::Data::INTERPRETED); \ identifier.name = "error.communication"; \ identifier.eventType = uscxml::Event::PLATFORM; #define ERROR_COMMUNICATION2(identifier, cause, node) \ uscxml::ErrorEvent identifier; \ identifier.data.compound["cause"] = uscxml::Data(cause, uscxml::Data::VERBATIM); \ + identifier.data.compound["file"] = uscxml::Data(uscxml::toStr(__FILE__), uscxml::Data::VERBATIM); \ + identifier.data.compound["line"] = uscxml::Data(uscxml::toStr(__LINE__), uscxml::Data::INTERPRETED); \ identifier.name = "error.communication"; \ identifier.data.compound["xpath"] = uscxml::Data(DOMUtils::xPathForNode(node), uscxml::Data::VERBATIM); \ identifier.eventType = uscxml::Event::PLATFORM; @@ -91,6 +101,8 @@ public: Event() : eventType(INTERNAL), hideSendId(false), uuid(UUID::getUUID()) {} explicit Event(const std::string& name, Type type = INTERNAL) : name(name), eventType(type), hideSendId(false) {} + static Event fromData(const Data& data); + bool operator< (const Event& other) const { return this < &other; } @@ -109,6 +121,8 @@ public: return name.size() > 0; } + operator Data(); + operator std::string() { std::stringstream ss; ss << *this; diff --git a/src/uscxml/plugins/Invoker.cpp b/src/uscxml/plugins/Invoker.cpp index a021ff7..82c15f3 100644 --- a/src/uscxml/plugins/Invoker.cpp +++ b/src/uscxml/plugins/Invoker.cpp @@ -40,4 +40,13 @@ XERCESC_NS::DOMElement* Invoker::getFinalize() { return _impl->getFinalize(); } +void Invoker::deserialize(const Data& encodedState) { + return _impl->deserialize(encodedState); +} + +Data Invoker::serialize() { + return _impl->serialize(); +} + + } diff --git a/src/uscxml/plugins/Invoker.h b/src/uscxml/plugins/Invoker.h index 2191e7b..bb01ddd 100644 --- a/src/uscxml/plugins/Invoker.h +++ b/src/uscxml/plugins/Invoker.h @@ -56,6 +56,11 @@ public: /// @copydoc InvokerImpl::getFinalize virtual XERCESC_NS::DOMElement* getFinalize(); + /// @copydoc InvokerImpl::deserialize + virtual void deserialize(const Data& encodedState); + + /// @copydoc InvokerImpl::serialize + virtual Data serialize(); protected: std::shared_ptr _impl; }; diff --git a/src/uscxml/plugins/InvokerImpl.h b/src/uscxml/plugins/InvokerImpl.h index b000ce2..e0446e1 100644 --- a/src/uscxml/plugins/InvokerImpl.h +++ b/src/uscxml/plugins/InvokerImpl.h @@ -90,6 +90,19 @@ public: _invokeId = invokeId; } + /** + * Load a state from a Data object + * @param encodedState The state we returned somewhen else via serialize. + */ + virtual void deserialize(const Data& encodedState) {} + + /** + * Save our state into a Data object + */ + virtual Data serialize() { + return Data(); + } + protected: /** * Return an event to the SCXML Interpreter instance. diff --git a/src/uscxml/plugins/datamodel/CMakeLists.txt b/src/uscxml/plugins/datamodel/CMakeLists.txt index e68fc8f..a208856 100644 --- a/src/uscxml/plugins/datamodel/CMakeLists.txt +++ b/src/uscxml/plugins/datamodel/CMakeLists.txt @@ -66,7 +66,7 @@ if (WITH_DM_PROMELA) promela/*.c promela/*.h ) - list (APPEND USCXML_FILES ${PROMELA_DATAMODEL}) + list (APPEND USCXML_FILES ${PROMELA_DATAMODEL}) endif() if (NOT SWIG_FOUND) diff --git a/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp b/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp index aae0111..22e8ccc 100644 --- a/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp +++ b/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp @@ -395,8 +395,14 @@ void JSCDataModel::setEvent(const Event& event) { } Data JSCDataModel::evalAsData(const std::string& content) { - JSValueRef result = evalAsValue(content); - return getValueAsData(result); + try { + JSValueRef result = evalAsValue(content); + return getValueAsData(result); + } catch (ErrorEvent e) { + // test453 vs test554 + throw e; +// return Data(content, Data::INTERPRETED); + } } Data JSCDataModel::getAsData(const std::string& content) { @@ -593,7 +599,7 @@ void JSCDataModel::setForeach(const std::string& item, const std::string& index, uint32_t iteration) { if (!isDeclared(item)) { - assign(item, Data()); + assign(item, Data("null", Data::INTERPRETED)); } // assign array element to item std::stringstream ss; @@ -710,7 +716,11 @@ void JSCDataModel::assign(const std::string& location, const Data& data) { void JSCDataModel::init(const std::string& location, const Data& data) { try { - assign(location, data); + if (data.empty()) { + assign(location, Data("null", Data::INTERPRETED)); + } else { + assign(location, data); + } } catch (ErrorEvent e) { // test 277 evalAsValue(location + " = undefined", true); diff --git a/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp b/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp index 868fee7..0f7cc24 100644 --- a/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp +++ b/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp @@ -75,6 +75,29 @@ void USCXMLInvoker::stop() { } } +void USCXMLInvoker::deserialize(const Data& encodedState) { + _invokedInterpreter.deserialize(encodedState["intepreter"]); +} + +Data USCXMLInvoker::serialize() { + Data encodedState; + + std::lock_guard lock(_mutex); + + InterpreterState state = USCXML_UNDEF; + while((state = _invokedInterpreter.getState())) { + if (state != USCXML_IDLE && state != USCXML_MACROSTEPPED && state != USCXML_FINISHED) { + _cond.wait(_mutex); + } else { + break; + } + } + + encodedState["intepreter"] = Data(_invokedInterpreter.serialize()); + + return encodedState; +} + void USCXMLInvoker::uninvoke() { _isActive = false; stop(); @@ -100,13 +123,9 @@ void USCXMLInvoker::run(void* instance) { InterpreterState state = USCXML_UNDEF; while(state != USCXML_FINISHED) { - state = INSTANCE->_invokedInterpreter.step(); - -// if (!INSTANCE->_isStarted) { -// // we have been cancelled -// INSTANCE->_isActive = false; -// return; -// } + std::lock_guard lock(INSTANCE->_mutex); + state = INSTANCE->_invokedInterpreter.step(200); + INSTANCE->_cond.notify_all(); } if (INSTANCE->_isActive) { @@ -143,10 +162,11 @@ void USCXMLInvoker::invoke(const std::string& source, const Event& invokeEvent) XERCESC_NS::DOMNode* newNode = document->importNode(invokeEvent.data.node, true); document->appendChild(newNode); -// std::cout << *document << std::endl; - // TODO: where do we get the namespace from? _invokedInterpreter = Interpreter::fromDocument(document, _interpreter->getBaseURL(), false); + } else if (invokeEvent.data.atom.size() > 0) { + // test530 when deserializing + _invokedInterpreter = Interpreter::fromXML(invokeEvent.data.atom, _interpreter->getBaseURL()); } else { _isActive = false; @@ -191,7 +211,8 @@ void USCXMLInvoker::invoke(const std::string& source, const Event& invokeEvent) start(); } else { - /// test 530 + // test 530 + // TODO: Is this the correct thing/place to do? Event e("done.invoke." + invokeEvent.invokeid, Event::PLATFORM); eventToSCXML(e, USCXML_INVOKER_SCXML_TYPE, _invokeId); _isActive = false; diff --git a/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h b/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h index 9509de3..78e7057 100644 --- a/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h +++ b/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h @@ -67,6 +67,9 @@ public: virtual void invoke(const std::string& source, const Event& invokeEvent); virtual void uninvoke(); + virtual void deserialize(const Data& encodedState); + virtual Data serialize(); + protected: void start(); @@ -78,6 +81,10 @@ protected: std::thread* _thread; EventQueue _parentQueue; Interpreter _invokedInterpreter; + + std::recursive_mutex _mutex; + std::condition_variable_any _cond; + }; #ifdef BUILD_AS_PLUGINS diff --git a/src/uscxml/util/DOM.cpp b/src/uscxml/util/DOM.cpp index bc628b7..f661ebb 100644 --- a/src/uscxml/util/DOM.cpp +++ b/src/uscxml/util/DOM.cpp @@ -252,6 +252,8 @@ void DOMUtils::inPostFixOrder(const std::set& elements, } } +//TODO: Unify recursive search in DOM + std::list DOMUtils::inDocumentOrder(const std::set& elements, const DOMElement* root, const bool includeEmbeddedDoc) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 874ed16..658d93c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -42,11 +42,12 @@ function(USCXML_TEST_COMPILE) endfunction() # simple one file tests -USCXML_TEST_COMPILE(NAME test-extensions LABEL general/test-extensions FILES src/test-extensions.cpp) +USCXML_TEST_COMPILE(NAME test-extensions LABEL general/test-extensions FILES src/test-extensions.cpp ../contrib/src/uscxml/PausableDelayedEventQueue.cpp) USCXML_TEST_COMPILE(NAME test-url LABEL general/test-url FILES src/test-url.cpp) USCXML_TEST_COMPILE(NAME test-lifecycle LABEL general/test-lifecycle FILES src/test-lifecycle.cpp) USCXML_TEST_COMPILE(NAME test-validating LABEL general/test-validating FILES src/test-validating.cpp) USCXML_TEST_COMPILE(NAME test-snippets LABEL general/test-snippets FILES src/test-snippets.cpp) +USCXML_TEST_COMPILE(NAME test-serialization LABEL general/test-serialization FILES src/test-serialization.cpp ../contrib/src/uscxml/PausableDelayedEventQueue.cpp) # USCXML_TEST_COMPILE(NAME test-c89-parser LABEL general/test-c89-parser FILES src/test-c89-parser.cpp) # test-stress is not an automated test diff --git a/test/src/test-extensions.cpp b/test/src/test-extensions.cpp index e3dfbb6..7686f9f 100644 --- a/test/src/test-extensions.cpp +++ b/test/src/test-extensions.cpp @@ -3,6 +3,7 @@ #include "uscxml/interpreter/InterpreterImpl.h" #include "uscxml/interpreter/BasicEventQueue.h" #include "uscxml/interpreter/BasicDelayedEventQueue.h" +#include "uscxml/PausableDelayedEventQueue.h" #include // for evutil_socket_t @@ -10,6 +11,21 @@ #include using namespace uscxml; +class MyPausableDelayedEventQueue; + +std::shared_ptr nestedDelayQueue; + +class MyPausableDelayedEventQueue : public PausableDelayedEventQueue { + MyPausableDelayedEventQueue(DelayedEventQueueCallbacks* callbacks) : PausableDelayedEventQueue(callbacks) { + } + + std::shared_ptr create(DelayedEventQueueCallbacks* callbacks) { + // remember as nestedDelayQueue in global scope + nestedDelayQueue = std::shared_ptr(new MyPausableDelayedEventQueue(callbacks)); + return nestedDelayQueue; + } +}; + // from issue 96: // https://github.com/tklab-tud/uscxml/issues/96 @@ -45,89 +61,6 @@ static const char *customDelayedEQ = " " ""; -class PausableDelayedEventQueue; -std::shared_ptr nestedDelayQueue; - -/** - * A DelayedEventQueue that implements pause/resume - */ -class PausableDelayedEventQueue : public BasicDelayedEventQueue { -public: - PausableDelayedEventQueue(DelayedEventQueueCallbacks* callbacks) : BasicDelayedEventQueue(callbacks) { - _pausedAt.tv_sec = 0; - _pausedAt.tv_usec = 0; - } - - std::shared_ptr create(DelayedEventQueueCallbacks* callbacks) { - // remember as nestedDelayQueue in global scope - nestedDelayQueue = std::shared_ptr(new PausableDelayedEventQueue(callbacks)); - return nestedDelayQueue; - } - - void pause() { - if(_pausedAt.tv_sec != 0 || _pausedAt.tv_usec != 0) { - return; // we are already paused! - } - - evutil_gettimeofday(&_pausedAt, NULL); // remember when we paused - - { - // Verbatim copy of stop() without cancelAllDelayed() - if (_isStarted) { - _isStarted = false; - event_base_loopbreak(_eventLoop); - } - if (_thread) { - _thread->join(); - delete _thread; - _thread = NULL; - } - } - - std::lock_guard lock(_mutex); - - // remove all events from libevent without deleting them - for(auto callbackData : _callbackData) { - Event data = callbackData.second.userData; - event_del(callbackData.second.event); - } - } - - void resume() { - if (_pausedAt.tv_sec != 0 || _pausedAt.tv_usec != 0) { - struct timeval now; - struct timeval pausedFor; - - evutil_gettimeofday(&now, NULL); - evutil_timersub(&now, &_pausedAt, &pausedFor); - _pausedAt.tv_sec = 0; - _pausedAt.tv_usec = 0; - - for(auto& callbackData : _callbackData) { - // add the time we were paused to all due times - evutil_timeradd(&callbackData.second.due, &pausedFor, &callbackData.second.due); - - struct timeval remain; - evutil_timersub(&callbackData.second.due, &now, &remain); - -#if 0 - std::cout << "Now : " << now.tv_sec << "." << now.tv_usec << std::endl; - std::cout << "Paused : " << pausedFor.tv_sec << "." << pausedFor.tv_usec << std::endl; - std::cout << "Remaining: " << remain.tv_sec << "." << remain.tv_usec << std::endl; -#endif - assert(remain.tv_usec >= 0 && remain.tv_sec >= 0); - - // reenqueue with libevent - event_add(callbackData.second.event, &remain); - } - } - start(); - } - -protected: - timeval _pausedAt; -}; - bool testPausableEventQueue() { Interpreter interpreter = Interpreter::fromXML(customDelayedEQ, ""); diff --git a/test/src/test-serialization.cpp b/test/src/test-serialization.cpp new file mode 100644 index 0000000..a19786e --- /dev/null +++ b/test/src/test-serialization.cpp @@ -0,0 +1,147 @@ +#define protected public + +#include "uscxml/config.h" +#include "uscxml/Interpreter.h" +#include "uscxml/interpreter/InterpreterImpl.h" +//#include "uscxml/Factory.h" +#include "uscxml/server/HTTPServer.h" + +#include "uscxml/interpreter/Logging.h" + +#include "uscxml/plugins/invoker/dirmon/DirMonInvoker.h" +#include + +#ifdef _WIN32 +#include "XGetopt.h" +#include "XGetopt.cpp" +#endif + +int startedAt; +int lastTransitionAt; + +//class StatusMonitor : public uscxml::InterpreterMonitor { +class StatusMonitor : public uscxml::StateTransitionMonitor { + void beforeTakingTransition(uscxml::Interpreter& interpreter, const XERCESC_NS::DOMElement* transition) { + lastTransitionAt = time(NULL); + } + +}; + +void printUsageAndExit() { + printf("test-serialization version " USCXML_VERSION " (" CMAKE_BUILD_TYPE " build - " CMAKE_COMPILER_STRING ")\n"); + printf("Usage\n"); + printf("\ttest-stress"); +#ifdef BUILD_AS_PLUGINS + printf(" [-p pluginPath]"); +#endif + printf(" \n"); + printf("\n"); + exit(1); +} + +int main(int argc, char** argv) { + using namespace uscxml; + + if (argc < 2) { + printUsageAndExit(); + } + + HTTPServer::getInstance(8188, 8189); +#ifndef _WIN32 + opterr = 0; +#endif + int option; + while ((option = getopt(argc, argv, "vl:p:")) != -1) { + switch(option) { + case 'p': + uscxml::Factory::setDefaultPluginPath(optarg); + break; + case '?': + break; + default: + printUsageAndExit(); + break; + } + } + + DirectoryWatch* watcher = new DirectoryWatch(argv[optind], true); + watcher->updateEntries(true); + + std::map entries = watcher->getAllEntries(); + + StatusMonitor vm; + vm.copyToInvokers(true); + + std::map::iterator entryIter = entries.begin(); + while(entryIter != entries.end()) { + if (!boost::ends_with(entryIter->first, ".scxml") || + entryIter->first.find("sub") != std::string::npos || + entryIter->first.find("test415.scxml") != std::string::npos || + entryIter->first.find("test329.scxml") != std::string::npos || + entryIter->first.find("test326.scxml") != std::string::npos || + entryIter->first.find("test307.scxml") != std::string::npos || + entryIter->first.find("test301.scxml") != std::string::npos || + entryIter->first.find("test230.scxml") != std::string::npos || + entryIter->first.find("test250.scxml") != std::string::npos || + entryIter->first.find("test178.scxml") != std::string::npos) { + entryIter++; + continue; + } + + startedAt = time(NULL); + lastTransitionAt = time(NULL); + + std::string sourceXML = std::string(argv[optind]) + PATH_SEPERATOR + entryIter->first; + sourceXML = "/Users/sradomski/Documents/TK/Code/uscxml/test/w3c/ecma/test557.scxml"; + + Interpreter interpreter = Interpreter::fromURL(sourceXML); + LOGD(USCXML_INFO) << "Processing " << interpreter.getImpl()->getBaseURL(); + if (interpreter) { + + interpreter.addMonitor(&vm); + std::string serializedState; + +RESTART_WITH_STATE: + InterpreterState state = InterpreterState::USCXML_UNDEF; + int now = time(NULL); + + try { +#if 1 + if (serializedState.size() > 0) { + Interpreter interpreter = Interpreter::fromURL(sourceXML); + interpreter.deserialize(serializedState); + } +#endif +// while(state != USCXML_FINISHED && now - startedAt < 20 && now - lastTransitionAt < 2) { + + while(state != USCXML_FINISHED) { + state = interpreter.step(); + +#if 1 + if (state == USCXML_MACROSTEPPED) { + assert(!interpreter.getImpl()->_internalQueue.dequeue(0)); + serializedState = interpreter.serialize(); +// std::cout << serializedState << std::endl; + goto RESTART_WITH_STATE; + } +#endif + + now = time(NULL); + } + assert(interpreter.isInState("pass")); + } catch (ErrorEvent e) { + LOGD(USCXML_ERROR) << e; + } catch (...) {} + } + entryIter++; + + // forever +// if (entryIter == entries.end()) { +// entryIter = entries.begin(); +// } + } + + delete watcher; + + return EXIT_SUCCESS; +} diff --git a/test/src/test-url.cpp b/test/src/test-url.cpp index 51fb545..94d5c91 100644 --- a/test/src/test-url.cpp +++ b/test/src/test-url.cpp @@ -153,6 +153,15 @@ void testFileURLs() { void testData() { { + Data data; + std::string key = "//state[@id=\"s0\"]/onentry[1]/send[1]"; + data[key] = Data("fooo"); + std::string json = data.asJSON(); + data = Data::fromJSON(json); + assert(data.hasKey(key)); + } + + { Data data = Data::fromJSON("{\"shiftKey\":false,\"toElement\":{\"id\":\"\",\"localName\":\"body\"},\"clientY\":38,\"y\":38,\"x\":66,\"ctrlKey\":false,\"relatedTarget\":{\"id\":\"\",\"localName\":\"body\"},\"clientX\":66,\"screenY\":288,\"metaKey\":false,\"offsetX\":58,\"altKey\":false,\"offsetY\":30,\"fromElement\":{\"id\":\"foo\",\"localName\":\"div\"},\"screenX\":-1691,\"dataTransfer\":null,\"button\":0,\"pageY\":38,\"layerY\":38,\"pageX\":66,\"charCode\":0,\"which\":0,\"keyCode\":0,\"detail\":0,\"layerX\":66,\"returnValue\":true,\"timeStamp\":1371223991895,\"eventPhase\":2,\"target\":{\"id\":\"foo\",\"localName\":\"div\"},\"defaultPrevented\":false,\"srcElement\":{\"id\":\"foo\",\"localName\":\"div\"},\"type\":\"mouseout\",\"cancelable\":true,\"currentTarget\":{\"id\":\"foo\",\"localName\":\"div\"},\"bubbles\":true,\"cancelBubble\":false}"); std::cout << data << std::endl; } @@ -266,11 +275,11 @@ int main(int argc, char** argv) { HTTPServer::getInstance(8199, 8200); try { - testPaths(); - testFileURLs(); - testHTTPURLs(); +// testPaths(); +// testFileURLs(); +// testHTTPURLs(); testData(); - testServlets(); +// testServlets(); } catch (Event e) { LOGD(USCXML_ERROR) << e; exit(EXIT_FAILURE); -- cgit v0.12