From aa6c3a1257a29cc5bcf8b94893732ee553f27582 Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Sun, 9 Sep 2012 01:26:39 +0200 Subject: Initial upload Still somewhat quirky to build but fairly feature complete --- .gitignore | 2 + CMakeLists.txt | 109 ++ contrib/cmake/FindArabica.cmake | 21 + contrib/cmake/FindEV.cmake | 20 + contrib/cmake/FindEVENT.cmake | 34 + contrib/cmake/FindGLOG.cmake | 20 + contrib/cmake/FindICONV.cmake | 57 + contrib/cmake/FindPION.cmake | 20 + contrib/cmake/FindV8.cmake | 28 + src/bindings/CMakeLists.txt | 12 + src/bindings/swig/java/CMakeLists.txt | 31 + src/bindings/swig/java/uscxml.i | 40 + src/uscxml.h | 6 + src/uscxml/Factory.cpp | 56 + src/uscxml/Factory.h | 73 + src/uscxml/Interpreter.cpp | 1712 ++++++++++++++++++++ src/uscxml/Interpreter.h | 314 ++++ src/uscxml/Message.cpp | 168 ++ src/uscxml/Message.h | 122 ++ src/uscxml/concurrency/BlockingQueue.h | 41 + .../eventqueue/libev/DelayedEventQueue.cpp | 57 + .../eventqueue/libev/DelayedEventQueue.h | 45 + .../eventqueue/libevent/DelayedEventQueue.cpp | 87 + .../eventqueue/libevent/DelayedEventQueue.h | 52 + src/uscxml/concurrency/tinythread.cpp | 303 ++++ src/uscxml/concurrency/tinythread.h | 714 ++++++++ src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp | 289 ++++ src/uscxml/datamodel/ecmascript/v8/V8DataModel.h | 73 + src/uscxml/ioprocessor/basichttp/README.md | 2 + .../basichttp/libevent/EventIOProcessor.cpp | 361 +++++ .../basichttp/libevent/EventIOProcessor.h | 96 ++ .../basichttp/mongoose/MongooseIOProcessor.cpp | 3 + .../basichttp/mongoose/MongooseIOProcessor.h | 15 + .../ioprocessor/basichttp/pion/PionIOProcessor.cpp | 74 + .../ioprocessor/basichttp/pion/PionIOProcessor.h | 47 + test/CMakeLists.txt | 29 + test/samples/apache/actions-initial-test.xml | 57 + test/samples/apache/actions-parallel-test.xml | 55 + test/samples/apache/actions-state-test.xml | 52 + test/samples/apache/assign-test-01.xml | 70 + test/samples/apache/assign-test-02.xml | 46 + test/samples/apache/bar.xml | 25 + test/samples/apache/custom-hello-world-01.xml | 37 + test/samples/apache/custom-hello-world-02.xml | 33 + test/samples/apache/custom-hello-world-03.xml | 33 + test/samples/apache/custom-hello-world-04-el.xml | 44 + test/samples/apache/custom-hello-world-04-jexl.xml | 62 + test/samples/apache/datamodel-01.xml | 107 ++ test/samples/apache/datamodel-02.xml | 83 + test/samples/apache/datamodel-03.xml | 259 +++ test/samples/apache/datamodel-04.xml | 48 + test/samples/apache/edit-profile-config.xml | 149 ++ test/samples/apache/eventdata-01.xml | 47 + test/samples/apache/eventdata-02.xml | 39 + test/samples/apache/eventdata-03.xml | 49 + test/samples/apache/eventdata-04.xml | 57 + test/samples/apache/external-hello-world.xml | 33 + test/samples/apache/foo.xml | 25 + test/samples/apache/hello-world.xml | 31 + test/samples/apache/history-deep-01.xml | 75 + test/samples/apache/history-default-01.xml | 90 + test/samples/apache/history-shallow-01.xml | 67 + test/samples/apache/invoked-01.xml | 32 + test/samples/apache/invoked-02.xml | 32 + test/samples/apache/invoked-03-01.xml | 33 + test/samples/apache/invoked-03.xml | 37 + test/samples/apache/invoker-01.xml | 38 + test/samples/apache/invoker-02.xml | 29 + test/samples/apache/invoker-03.xml | 39 + test/samples/apache/invoker-04.xml | 55 + test/samples/apache/issue62-01-ext.xml | 27 + test/samples/apache/issue62-01.xml | 29 + test/samples/apache/issue62-02-ext.xml | 39 + test/samples/apache/issue62-02.xml | 35 + test/samples/apache/issue62-03-ext.xml | 39 + test/samples/apache/issue62-03.xml | 35 + test/samples/apache/issue64-01.xml | 39 + test/samples/apache/issue64-02.xml | 55 + test/samples/apache/jsp-rootctx-test.xml | 31 + test/samples/apache/log-on-config.xml | 81 + test/samples/apache/microwave-01.xml | 78 + test/samples/apache/microwave-02-legacy.xml | 87 + test/samples/apache/microwave-02.xml | 87 + test/samples/apache/microwave-03.xml | 79 + test/samples/apache/microwave-04.xml | 90 + test/samples/apache/microwave-05.xml | 98 ++ test/samples/apache/parallel-01.xml | 70 + test/samples/apache/parallel-02.xml | 31 + test/samples/apache/parallel-03.xml | 118 ++ test/samples/apache/prefix-01.xml | 29 + test/samples/apache/scxml-initial-attr.xml | 25 + test/samples/apache/send-01.xml | 47 + test/samples/apache/send-02.xml | 89 + test/samples/apache/src-test-1.xml | 31 + test/samples/apache/src-test-2.xml | 30 + test/samples/apache/src-test-3.xml | 28 + test/samples/apache/src-test-4.xml | 30 + test/samples/apache/src-test-5.xml | 30 + test/samples/apache/state-01.xml | 24 + test/samples/apache/stateless-01.xml | 50 + test/samples/apache/stateless-parallel-01.xml | 54 + test/samples/apache/static-method.xml | 32 + test/samples/apache/stopwatch.xml | 41 + test/samples/apache/tie-breaker-01.xml | 35 + test/samples/apache/tie-breaker-02.xml | 45 + test/samples/apache/tie-breaker-03.xml | 56 + test/samples/apache/tie-breaker-04.xml | 36 + test/samples/apache/tie-breaker-05.xml | 64 + test/samples/apache/tie-breaker-06.xml | 40 + test/samples/apache/transitions-01-legacy.xml | 140 ++ test/samples/apache/transitions-01.xml | 136 ++ test/samples/apache/transitions-02.xml | 53 + test/samples/apache/transitions-03.xml | 88 + test/samples/apache/transitions-04.xml | 93 ++ test/samples/apache/transitions-05.xml | 38 + test/samples/apache/travel-dialog.xml | 54 + test/samples/apache/wildcard-01.xml | 35 + test/samples/apache/wildcard-02.xml | 32 + test/samples/apache/wizard-01.xml | 41 + test/samples/apache/wizard-02.xml | 65 + test/samples/w3c/Blackjack.scxml | 99 ++ test/samples/w3c/Main.scxml | 204 +++ test/samples/w3c/Test2Sub1.xml | 9 + test/samples/w3c/TrafficReport.scxml | 88 + test/samples/w3c/calc.scxml | 158 ++ test/samples/w3c/edit-profile-config.scxml | 70 + test/samples/w3c/log-on-config.scxml | 32 + test/samples/w3c/microwave-01.scxml | 50 + test/samples/w3c/microwave-02.scxml | 63 + test/samples/w3c/simple.xml | 9 + test/schema/scxml-message.xsd | 122 ++ test/schema/scxml.xsd | 176 ++ test/src/test-apache-commons.cpp | 128 ++ test/src/test-communication.cpp | 26 + test/src/test-communication.scxml | 35 + test/src/test-ecmascript-v8.cpp | 44 + test/src/test-ecmascript.scxml | 112 ++ test/src/test-eventdelay.cpp | 39 + test/src/test-execution.cpp | 16 + test/src/test-execution.scxml | 56 + test/src/test-predicates.cpp | 60 + test/src/test-predicates.scxml | 9 + 142 files changed, 11445 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 contrib/cmake/FindArabica.cmake create mode 100644 contrib/cmake/FindEV.cmake create mode 100644 contrib/cmake/FindEVENT.cmake create mode 100644 contrib/cmake/FindGLOG.cmake create mode 100644 contrib/cmake/FindICONV.cmake create mode 100644 contrib/cmake/FindPION.cmake create mode 100644 contrib/cmake/FindV8.cmake create mode 100644 src/bindings/CMakeLists.txt create mode 100644 src/bindings/swig/java/CMakeLists.txt create mode 100644 src/bindings/swig/java/uscxml.i create mode 100644 src/uscxml.h create mode 100644 src/uscxml/Factory.cpp create mode 100644 src/uscxml/Factory.h create mode 100644 src/uscxml/Interpreter.cpp create mode 100644 src/uscxml/Interpreter.h create mode 100644 src/uscxml/Message.cpp create mode 100644 src/uscxml/Message.h create mode 100644 src/uscxml/concurrency/BlockingQueue.h create mode 100644 src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.cpp create mode 100644 src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.h create mode 100644 src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.cpp create mode 100644 src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h create mode 100644 src/uscxml/concurrency/tinythread.cpp create mode 100644 src/uscxml/concurrency/tinythread.h create mode 100644 src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp create mode 100644 src/uscxml/datamodel/ecmascript/v8/V8DataModel.h create mode 100644 src/uscxml/ioprocessor/basichttp/README.md create mode 100644 src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp create mode 100644 src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h create mode 100644 src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.cpp create mode 100644 src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.h create mode 100644 src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.cpp create mode 100644 src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h create mode 100644 test/CMakeLists.txt create mode 100644 test/samples/apache/actions-initial-test.xml create mode 100644 test/samples/apache/actions-parallel-test.xml create mode 100644 test/samples/apache/actions-state-test.xml create mode 100644 test/samples/apache/assign-test-01.xml create mode 100644 test/samples/apache/assign-test-02.xml create mode 100644 test/samples/apache/bar.xml create mode 100644 test/samples/apache/custom-hello-world-01.xml create mode 100644 test/samples/apache/custom-hello-world-02.xml create mode 100644 test/samples/apache/custom-hello-world-03.xml create mode 100644 test/samples/apache/custom-hello-world-04-el.xml create mode 100644 test/samples/apache/custom-hello-world-04-jexl.xml create mode 100644 test/samples/apache/datamodel-01.xml create mode 100644 test/samples/apache/datamodel-02.xml create mode 100644 test/samples/apache/datamodel-03.xml create mode 100644 test/samples/apache/datamodel-04.xml create mode 100644 test/samples/apache/edit-profile-config.xml create mode 100644 test/samples/apache/eventdata-01.xml create mode 100644 test/samples/apache/eventdata-02.xml create mode 100644 test/samples/apache/eventdata-03.xml create mode 100644 test/samples/apache/eventdata-04.xml create mode 100644 test/samples/apache/external-hello-world.xml create mode 100644 test/samples/apache/foo.xml create mode 100644 test/samples/apache/hello-world.xml create mode 100644 test/samples/apache/history-deep-01.xml create mode 100644 test/samples/apache/history-default-01.xml create mode 100644 test/samples/apache/history-shallow-01.xml create mode 100644 test/samples/apache/invoked-01.xml create mode 100644 test/samples/apache/invoked-02.xml create mode 100644 test/samples/apache/invoked-03-01.xml create mode 100644 test/samples/apache/invoked-03.xml create mode 100644 test/samples/apache/invoker-01.xml create mode 100644 test/samples/apache/invoker-02.xml create mode 100644 test/samples/apache/invoker-03.xml create mode 100644 test/samples/apache/invoker-04.xml create mode 100644 test/samples/apache/issue62-01-ext.xml create mode 100644 test/samples/apache/issue62-01.xml create mode 100644 test/samples/apache/issue62-02-ext.xml create mode 100644 test/samples/apache/issue62-02.xml create mode 100644 test/samples/apache/issue62-03-ext.xml create mode 100644 test/samples/apache/issue62-03.xml create mode 100644 test/samples/apache/issue64-01.xml create mode 100644 test/samples/apache/issue64-02.xml create mode 100644 test/samples/apache/jsp-rootctx-test.xml create mode 100644 test/samples/apache/log-on-config.xml create mode 100644 test/samples/apache/microwave-01.xml create mode 100644 test/samples/apache/microwave-02-legacy.xml create mode 100644 test/samples/apache/microwave-02.xml create mode 100644 test/samples/apache/microwave-03.xml create mode 100644 test/samples/apache/microwave-04.xml create mode 100644 test/samples/apache/microwave-05.xml create mode 100644 test/samples/apache/parallel-01.xml create mode 100644 test/samples/apache/parallel-02.xml create mode 100644 test/samples/apache/parallel-03.xml create mode 100644 test/samples/apache/prefix-01.xml create mode 100644 test/samples/apache/scxml-initial-attr.xml create mode 100644 test/samples/apache/send-01.xml create mode 100644 test/samples/apache/send-02.xml create mode 100644 test/samples/apache/src-test-1.xml create mode 100644 test/samples/apache/src-test-2.xml create mode 100644 test/samples/apache/src-test-3.xml create mode 100644 test/samples/apache/src-test-4.xml create mode 100644 test/samples/apache/src-test-5.xml create mode 100644 test/samples/apache/state-01.xml create mode 100644 test/samples/apache/stateless-01.xml create mode 100644 test/samples/apache/stateless-parallel-01.xml create mode 100644 test/samples/apache/static-method.xml create mode 100644 test/samples/apache/stopwatch.xml create mode 100644 test/samples/apache/tie-breaker-01.xml create mode 100644 test/samples/apache/tie-breaker-02.xml create mode 100644 test/samples/apache/tie-breaker-03.xml create mode 100644 test/samples/apache/tie-breaker-04.xml create mode 100644 test/samples/apache/tie-breaker-05.xml create mode 100644 test/samples/apache/tie-breaker-06.xml create mode 100644 test/samples/apache/transitions-01-legacy.xml create mode 100644 test/samples/apache/transitions-01.xml create mode 100644 test/samples/apache/transitions-02.xml create mode 100644 test/samples/apache/transitions-03.xml create mode 100644 test/samples/apache/transitions-04.xml create mode 100644 test/samples/apache/transitions-05.xml create mode 100644 test/samples/apache/travel-dialog.xml create mode 100644 test/samples/apache/wildcard-01.xml create mode 100644 test/samples/apache/wildcard-02.xml create mode 100644 test/samples/apache/wizard-01.xml create mode 100644 test/samples/apache/wizard-02.xml create mode 100644 test/samples/w3c/Blackjack.scxml create mode 100644 test/samples/w3c/Main.scxml create mode 100644 test/samples/w3c/Test2Sub1.xml create mode 100644 test/samples/w3c/TrafficReport.scxml create mode 100644 test/samples/w3c/calc.scxml create mode 100644 test/samples/w3c/edit-profile-config.scxml create mode 100644 test/samples/w3c/log-on-config.scxml create mode 100644 test/samples/w3c/microwave-01.scxml create mode 100644 test/samples/w3c/microwave-02.scxml create mode 100644 test/samples/w3c/simple.xml create mode 100644 test/schema/scxml-message.xsd create mode 100644 test/schema/scxml.xsd create mode 100644 test/src/test-apache-commons.cpp create mode 100644 test/src/test-communication.cpp create mode 100644 test/src/test-communication.scxml create mode 100644 test/src/test-ecmascript-v8.cpp create mode 100644 test/src/test-ecmascript.scxml create mode 100644 test/src/test-eventdelay.cpp create mode 100644 test/src/test-execution.cpp create mode 100644 test/src/test-execution.scxml create mode 100644 test/src/test-predicates.cpp create mode 100644 test/src/test-predicates.scxml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f77e4ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +*.DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c462751 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,109 @@ +cmake_minimum_required(VERSION 2.8.4) + +project(uscxml) + +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/contrib/cmake) + +set(USCXML_LIBS) +set(USCXML_FILES) + +# libxml2 +find_package(LibXml2 REQUIRED) +include_directories(${LIBXML2_INCLUDE_DIR}) +list (APPEND USCXML_LIBS ${LIBXML2_LIBRARIES}) + +# prefer rest as static libraries +set(CMAKE_FIND_LIBRARY_SUFFIXES .lib .a ${CMAKE_FIND_LIBRARY_SUFFIXES}) + +#glog +set(ENV{GLOG_SRC} ${CMAKE_SOURCE_DIR}/../glog) +find_package(GLOG REQUIRED) +include_directories(${GLOG_INCLUDE_DIR}) +list (APPEND USCXML_LIBS ${GLOG_LIBRARY}) + +# arabica +find_package(Arabica REQUIRED) +include_directories(${ARABICA_INCLUDE_DIR}) +list (APPEND USCXML_LIBS ${ARABICA_LIBRARY}) + +# boost - header only +FIND_PATH(Boost_INCLUDE_DIR boost/version.hpp PATHS /usr/include) +include_directories(${Boost_INCLUDE_DIR}) + +#event +set(ENV{EVENT_SRC} ${CMAKE_SOURCE_DIR}/../libevent) +find_package(EVENT REQUIRED) +include_directories(${EVENT_INCLUDE_DIR}) +file(GLOB_RECURSE USCXML_IO_PROCESSOR_LIBEVENT + src/uscxml/ioprocessor/basichttp/libevent/*.cpp + src/uscxml/ioprocessor/basichttp/libevent/*.h +) +list (APPEND USCXML_FILES ${USCXML_IO_PROCESSOR_LIBEVENT}) + +file(GLOB_RECURSE USCXML_EVENTQUEUE_LIBEVENT + src/uscxml/concurrency/eventqueue/libevent/*.cpp + src/uscxml/concurrency/eventqueue/libevent/*.h +) +list (APPEND USCXML_FILES ${USCXML_EVENTQUEUE_LIBEVENT}) + +list (APPEND USCXML_LIBS ${EVENT_LIBRARY}) + +# v8 +set(ENV{V8_SRC} ${CMAKE_SOURCE_DIR}/../v8) +find_package(V8 REQUIRED) +include_directories(${V8_INCLUDE_DIR}) +file(GLOB_RECURSE USCXML_DATAMODEL_V8 + src/uscxml/datamodel/ecmascript/v8/*.cpp + src/uscxml/datamodel/ecmascript/v8/*.h +) +list (APPEND USCXML_FILES ${USCXML_DATAMODEL_V8}) +list (APPEND USCXML_LIBS ${V8_LIBRARY}) + + +file(GLOB USCXML_CONCURRENCY src/uscxml/concurrency/*.cpp src/uscxml/concurrency/*.h) +list (APPEND USCXML_FILES ${USCXML_CONCURRENCY}) + +file(GLOB USCXML_INTERPRETER src/uscxml/*.cpp src/uscxml/*.h) +list (APPEND USCXML_FILES ${USCXML_INTERPRETER}) + +include_directories(src) + +# build library +add_library(uscxml ${USCXML_FILES}) +target_link_libraries(uscxml ${USCXML_LIBS}) + +add_subdirectory(test) +add_subdirectory(src/bindings) + +# ---- OLD DEPENDENCIES ------------- + +#set(Boost_DEBUG 1) +# find_package(Boost COMPONENTS thread regex system date_time REQUIRED) +# INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIR}) + +#openssl - needed for pion +# find_package(OPENSSL REQUIRED) +# INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) + +#zlib - needed for pion +# find_package(ZLIB REQUIRED) +# INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS}) + +#iconv - needed for pion +# find_package(ICONV REQUIRED) +# INCLUDE_DIRECTORIES(${ICONV_INCLUDE_DIR}) + +#ev - no longer supported +# set(ENV{EV_SRC} ${CMAKE_SOURCE_DIR}/../libev) +# find_package(EV REQUIRED) +# include_directories(${EV_INCLUDE_DIR}) + +#pion +# set(ENV{PION_SRC} ${CMAKE_SOURCE_DIR}/../pion) +# find_package(PION REQUIRED) +# include_directories(${PION_INCLUDE_DIR}) +# file(GLOB_RECURSE USCXML_IO_PROCESSOR_PION +# src/uscxml/ioprocessor/basichttp/pion/*.cpp +# src/uscxml/ioprocessor/basichttp/pion/*.h +# ) + diff --git a/contrib/cmake/FindArabica.cmake b/contrib/cmake/FindArabica.cmake new file mode 100644 index 0000000..bd4fe18 --- /dev/null +++ b/contrib/cmake/FindArabica.cmake @@ -0,0 +1,21 @@ +FIND_PATH(ARABICA_INCLUDE_DIR Arabica/getparam.hpp + PATH_SUFFIXES include/arabica include + PATHS + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt + HINTS $ENV{ARABICA_SRC} +) + +FIND_LIBRARY(ARABICA_LIBRARY + NAMES arabica + HINTS $ENV{ARABICA_SRC}/src/.libs/ + +) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Arabica DEFAULT_MSG ARABICA_LIBRARY ARABICA_INCLUDE_DIR) +MARK_AS_ADVANCED(ARABICA_LIBRARY ARABICA_INCLUDE_DIR) diff --git a/contrib/cmake/FindEV.cmake b/contrib/cmake/FindEV.cmake new file mode 100644 index 0000000..f994aa1 --- /dev/null +++ b/contrib/cmake/FindEV.cmake @@ -0,0 +1,20 @@ +FIND_PATH(EV_INCLUDE_DIR ev.h + PATH_SUFFIXES include + PATHS + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt + HINTS $ENV{EV_SRC} +) + +FIND_LIBRARY(EV_LIBRARY + NAMES ev + HINTS $ENV{EV_SRC}/.libs/ +) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(EV DEFAULT_MSG EV_LIBRARY EV_INCLUDE_DIR) +MARK_AS_ADVANCED(EV_LIBRARY EV_INCLUDE_DIR) diff --git a/contrib/cmake/FindEVENT.cmake b/contrib/cmake/FindEVENT.cmake new file mode 100644 index 0000000..31e074c --- /dev/null +++ b/contrib/cmake/FindEVENT.cmake @@ -0,0 +1,34 @@ +FIND_PATH(EVENT_INCLUDE_DIR event2/event.h + PATH_SUFFIXES include + PATHS + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt + HINTS $ENV{EVENT_SRC} +) + +FIND_LIBRARY(EVENT_LIBRARY + NAMES event + HINTS $ENV{EVENT_SRC}/.libs/ +) + +if (NOT WIN32) + FIND_LIBRARY(EVENT_LIBRARY_THREADS + NAMES event_pthreads + HINTS $ENV{EVENT_SRC}/.libs/ + ) +else() + FIND_LIBRARY(EVENT_LIBRARY_THREADS + NAMES event_pthreads + HINTS $ENV{EVENT_SRC}/.libs/ + ) +endif() + +list (APPEND EVENT_LIBRARY ${EVENT_LIBRARY_THREADS}) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(EVENT DEFAULT_MSG EVENT_LIBRARY EVENT_INCLUDE_DIR) +MARK_AS_ADVANCED(EVENT_LIBRARY EVENT_INCLUDE_DIR) diff --git a/contrib/cmake/FindGLOG.cmake b/contrib/cmake/FindGLOG.cmake new file mode 100644 index 0000000..fee9d78 --- /dev/null +++ b/contrib/cmake/FindGLOG.cmake @@ -0,0 +1,20 @@ +FIND_PATH(GLOG_INCLUDE_DIR glog/logging.h + PATH_SUFFIXES include src + PATHS + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt + HINTS $ENV{GLOG_SRC} +) + +FIND_LIBRARY(GLOG_LIBRARY + NAMES glog + HINTS $ENV{GLOG_SRC}/.libs/ +) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(GLOG DEFAULT_MSG GLOG_LIBRARY GLOG_INCLUDE_DIR) +MARK_AS_ADVANCED(GLOG_LIBRARY GLOG_INCLUDE_DIR) diff --git a/contrib/cmake/FindICONV.cmake b/contrib/cmake/FindICONV.cmake new file mode 100644 index 0000000..a95bc65 --- /dev/null +++ b/contrib/cmake/FindICONV.cmake @@ -0,0 +1,57 @@ +# - Try to find Iconv +# Once done this will define +# +# ICONV_FOUND - system has Iconv +# ICONV_INCLUDE_DIR - the Iconv include directory +# ICONV_LIBRARIES - Link these to use Iconv +# ICONV_SECOND_ARGUMENT_IS_CONST - the second argument for iconv() is const +# +include(CheckCXXSourceCompiles) + +IF (ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) + # Already in cache, be silent + SET(ICONV_FIND_QUIETLY TRUE) +ENDIF (ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) + +FIND_PATH(ICONV_INCLUDE_DIR iconv.h) + +FIND_LIBRARY(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c) + +IF(ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) + SET(ICONV_FOUND TRUE) +ENDIF(ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) + +set(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIR}) +set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARIES}) +IF(ICONV_FOUND) + check_cxx_source_compiles(" + #include + int main(){ + iconv_t conv = 0; + const char* in = 0; + size_t ilen = 0; + char* out = 0; + size_t olen = 0; + iconv(conv, &in, &ilen, &out, &olen); + return 0; + } +" ICONV_SECOND_ARGUMENT_IS_CONST ) +ENDIF(ICONV_FOUND) +set(CMAKE_REQUIRED_INCLUDES) +set(CMAKE_REQUIRED_LIBRARIES) + +IF(ICONV_FOUND) + IF(NOT ICONV_FIND_QUIETLY) + MESSAGE(STATUS "Found Iconv: ${ICONV_LIBRARIES}") + ENDIF(NOT ICONV_FIND_QUIETLY) +ELSE(ICONV_FOUND) + IF(Iconv_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find Iconv") + ENDIF(Iconv_FIND_REQUIRED) +ENDIF(ICONV_FOUND) + +MARK_AS_ADVANCED( + ICONV_INCLUDE_DIR + ICONV_LIBRARIES + ICONV_SECOND_ARGUMENT_IS_CONST +) \ No newline at end of file diff --git a/contrib/cmake/FindPION.cmake b/contrib/cmake/FindPION.cmake new file mode 100644 index 0000000..3a4157a --- /dev/null +++ b/contrib/cmake/FindPION.cmake @@ -0,0 +1,20 @@ +FIND_PATH(PION_INCLUDE_DIR pion/config.hpp + PATH_SUFFIXES include + PATHS + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt + HINTS $ENV{PION_SRC} +) + +FIND_LIBRARY(PION_LIBRARY + NAMES pion + HINTS $ENV{PION_SRC}/src/.libs/ +) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(PION DEFAULT_MSG PION_LIBRARY PION_INCLUDE_DIR) +MARK_AS_ADVANCED(PION_LIBRARY PION_INCLUDE_DIR) diff --git a/contrib/cmake/FindV8.cmake b/contrib/cmake/FindV8.cmake new file mode 100644 index 0000000..6836fb3 --- /dev/null +++ b/contrib/cmake/FindV8.cmake @@ -0,0 +1,28 @@ +FIND_PATH(V8_INCLUDE_DIR v8.h + PATH_SUFFIXES include + PATHS + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt + HINTS $ENV{V8_SRC} +) + +FIND_LIBRARY(V8_LIBRARY_BASE + NAMES v8_base + HINTS $ENV{V8_SRC}/out/native/ +) +list(APPEND V8_LIBRARY ${V8_LIBRARY_BASE}) + +FIND_LIBRARY(V8_LIBRARY_SNAPSHOT + NAMES v8_snapshot + HINTS $ENV{V8_SRC}/out/native/ + +) +list(APPEND V8_LIBRARY ${V8_LIBRARY_SNAPSHOT}) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(V8 DEFAULT_MSG V8_LIBRARY V8_INCLUDE_DIR) +MARK_AS_ADVANCED(V8_LIBRARY V8_INCLUDE_DIR) diff --git a/src/bindings/CMakeLists.txt b/src/bindings/CMakeLists.txt new file mode 100644 index 0000000..deac396 --- /dev/null +++ b/src/bindings/CMakeLists.txt @@ -0,0 +1,12 @@ +find_package(SWIG) +if (SWIG_FOUND) + if(SWIG_VERSION VERSION_GREATER 2.0.4) + MARK_AS_ADVANCED(SWIG_DIR SWIG_EXECUTABLE SWIG_VERSION) + INCLUDE(${SWIG_USE_FILE}) + add_subdirectory(swig/java) + else() + message("SWIG version 2.0.5 is required, found ${SWIG_VERSION} - skipping java wrapper generation") + endif() +else() + message("SWIG not found - skipping wrapper generation") +endif() diff --git a/src/bindings/swig/java/CMakeLists.txt b/src/bindings/swig/java/CMakeLists.txt new file mode 100644 index 0000000..a729b8e --- /dev/null +++ b/src/bindings/swig/java/CMakeLists.txt @@ -0,0 +1,31 @@ +# generate JNI library and create a jar +# Make from within Eclipse fails miserably with the whole thing + +find_package(JNI) +if(JNI_FOUND) + include_directories(${JNI_INCLUDE_DIRS}) +else() + message(STATUS "No JNI libraries found - not building Java wrappers") + return() +endif() + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +SET(CMAKE_SWIG_FLAGS "") +SET(SCXMLPL_JAVA_PACKAGE "org.uscxml") +SET(SCXMLPL_JAVA_DIR "org/uscxml") + +# we need ; to produce a space with the package .. weird +SET_SOURCE_FILES_PROPERTIES(uscxml.i PROPERTIES SWIG_FLAGS "-package;${SCXMLPL_JAVA_PACKAGE}") +SET_SOURCE_FILES_PROPERTIES(uscxml.i PROPERTIES CPLUSPLUS ON) +SET(CMAKE_SWIG_OUTDIR "${CMAKE_CURRENT_BINARY_DIR}/${SCXMLPL_JAVA_DIR}") + +SWIG_ADD_MODULE(uscxmlNativeJava java uscxml.i) +foreach(JNI_LIBRARY ${JNI_LIBRARIES}) + if (NOT ${JNI_LIBRARY} MATCHES ".*jawt.*") + SWIG_LINK_LIBRARIES(uscxmlNativeJava ${JNI_LIBRARY}) + endif() +endforeach() +set_target_properties(uscxmlNativeJava PROPERTIES FOLDER "Bindings") + +swig_link_libraries(uscxmlNativeJava uscxml) diff --git a/src/bindings/swig/java/uscxml.i b/src/bindings/swig/java/uscxml.i new file mode 100644 index 0000000..ccd6fe0 --- /dev/null +++ b/src/bindings/swig/java/uscxml.i @@ -0,0 +1,40 @@ +%module(directors="1", allprotected="1") uscxmlNativeJava + +// import swig typemaps +//%include +//%include +%include + +// disable warning related to unknown base class +#pragma SWIG nowarn=401 +//%ignore boost::enable_shared_from_this; + +%javaconst(1); + +# %shared_ptr(uscxml::dom::Element); +# %shared_ptr(uscxml::dom::Executable); + + +//************************************************** +// This ends up in the generated wrapper code +//************************************************** + +%{ + +#include "../../../uscxml/Message.h" +#include "../../../uscxml/Interpreter.h" + +using namespace uscxml; + +%} + +%rename(toString) operator<<; + + +//*********************************************** +// Parse the header file to generate wrappers +//*********************************************** + +%include "../../../uscxml/Message.h" +%include "../../../uscxml/Interpreter.h" + diff --git a/src/uscxml.h b/src/uscxml.h new file mode 100644 index 0000000..792ba43 --- /dev/null +++ b/src/uscxml.h @@ -0,0 +1,6 @@ +#ifndef USCXML_H_2WZ0PBQH +#define USCXML_H_2WZ0PBQH + +#include "uscxml/Interpreter.h" + +#endif /* end of include guard: USCXML_H_2WZ0PBQH */ diff --git a/src/uscxml/Factory.cpp b/src/uscxml/Factory.cpp new file mode 100644 index 0000000..91da562 --- /dev/null +++ b/src/uscxml/Factory.cpp @@ -0,0 +1,56 @@ +#include "uscxml/Factory.h" +#include "uscxml/datamodel/ecmascript/v8/V8DataModel.h" +//#include "uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h" +#include "uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h" + +namespace uscxml { + + Factory::Factory() { + _dataModels["ecmascript"] = new V8DataModel(); +// _ioProcessors["basichttp"] = new PionIOProcessor(); + _ioProcessors["basichttp"] = new io::libevent::EventIOProcessor(); + } + + void Factory::registerIOProcessor(const std::string type, IOProcessor* ioProcessor) { + getInstance()->_ioProcessors[type] = ioProcessor; + } + + void Factory::registerDataModel(const std::string type, DataModel* dataModel) { + getInstance()->_dataModels[type] = dataModel; + } + + void Factory::registerExecutableContent(const std::string tag, ExecutableContent* executableContent) { + getInstance()->_executableContent[tag] = executableContent; + } + + DataModel* Factory::getDataModel(const std::string type, Interpreter* interpreter) { + if (Factory::getInstance()->_dataModels.find(type) != getInstance()->_dataModels.end()) { + return getInstance()->_dataModels[type]->create(interpreter); + } + return NULL; + } + + IOProcessor* Factory::getIOProcessor(const std::string type, Interpreter* interpreter) { + if (getInstance()->_ioProcessors.find(type) != getInstance()->_ioProcessors.end()) { + return getInstance()->_ioProcessors[type]->create(interpreter); + } + return NULL; + } + + ExecutableContent* Factory::getExecutableContent(const std::string tag, Interpreter* interpreter) { + if (getInstance()->_executableContent.find(tag) != getInstance()->_executableContent.end()) { + return getInstance()->_executableContent[tag]->create(interpreter); + } + return NULL; + } + + Factory* Factory::getInstance() { + if (_instance == NULL) { + _instance = new Factory(); + } + return _instance; + } + + Factory* Factory::_instance = NULL; + +} \ No newline at end of file diff --git a/src/uscxml/Factory.h b/src/uscxml/Factory.h new file mode 100644 index 0000000..f1840c6 --- /dev/null +++ b/src/uscxml/Factory.h @@ -0,0 +1,73 @@ +#ifndef FACTORY_H_5WKLGPRB +#define FACTORY_H_5WKLGPRB + +#include "uscxml/Message.h" + +#include + +namespace uscxml { + + class Interpreter; + + class ExecutableContent { + public: + ExecutableContent() {}; + virtual ExecutableContent* create(Interpreter* interpreter) = 0; + }; + + class IOProcessor { + public: + IOProcessor() {}; + virtual ~IOProcessor() {}; + virtual IOProcessor* create(Interpreter* interpreter) = 0; + + virtual std::string getURL() = 0; + virtual void send(SendRequest& req) = 0; + virtual void invoke(InvokeRequest& req) = 0; + virtual void cancel(const std::string sendId) = 0; + }; + + class DataModel { + public: + virtual DataModel* create(Interpreter* interpreter) = 0; + + virtual bool validate(const std::string& location, const std::string& schema) = 0; + virtual void setEvent(Event& event) = 0; + virtual void setData(const std::string& key, Data& event) = 0; + + // foreach + virtual uint32_t getLength(const std::string& expr) = 0; + virtual void pushContext() = 0; + virtual void popContext() = 0; + + virtual void eval(const std::string& expr) = 0; + virtual std::string evalAsString(const std::string& expr) = 0; + virtual bool evalAsBool(const std::string& expr) = 0; + virtual void assign(const std::string& location, const std::string& expr) = 0; + }; + + class Factory { + public: + static void registerIOProcessor(const std::string type, IOProcessor* ioProcessor); + static void registerDataModel(const std::string type, DataModel* dataModel); + static void registerExecutableContent(const std::string tag, ExecutableContent* executableContent); + + static DataModel* getDataModel(const std::string type, Interpreter* interpreter); + static IOProcessor* getIOProcessor(const std::string type, Interpreter* interpreter); + static ExecutableContent* getExecutableContent(const std::string tag, Interpreter* interpreter); + static Factory* getInstance(); + + std::map _dataModels; + std::map _ioProcessors; + std::map _executableContent; + + protected: + Factory(); + static Factory* _instance; + + }; + + +} + +#endif /* end of include guard: FACTORY_H_5WKLGPRB */ diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp new file mode 100644 index 0000000..7189f12 --- /dev/null +++ b/src/uscxml/Interpreter.cpp @@ -0,0 +1,1712 @@ +#include "uscxml/Interpreter.h" + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace uscxml { + +using namespace Arabica::XPath; +using namespace Arabica::DOM; + +boost::uuids::random_generator Interpreter::uuidGen; +const std::string Interpreter::getUUID() { + return boost::lexical_cast(uuidGen()); +} + +Interpreter::Interpreter(const std::string& url) { + _thread = NULL; + _url = url; + + LOG(INFO) << "runtime started with " << _url; + Arabica::SAX::InputSource inputSource(_url); + + Arabica::SAX2DOM::Parser domParser; + Arabica::SAX::CatchErrorHandler errorHandler; + domParser.setErrorHandler(errorHandler); + if(!domParser.parse(inputSource) || !domParser.getDocument().hasChildNodes()) { + LOG(INFO) << "could not parse " << _url << ":"; + if(errorHandler.errorsReported()) { + LOG(ERROR) << errorHandler.errors() << std::endl; + } else { + Arabica::SAX::InputSourceResolver resolver(inputSource, Arabica::default_string_adaptor()); + if (!resolver.resolve()) { + LOG(ERROR) << "no such file"; + } + } + } else { + _doc = domParser.getDocument(); + } + + if (_doc) { + // do we have a xmlns attribute? + std::string ns = _doc.getDocumentElement().getNamespaceURI(); + if(ns.size() > 0) { + _nsContext.addNamespaceDeclaration(ns, "sc"); + _xpath.setNamespaceContext(_nsContext); + _nsPrefix = "sc:"; + } + NodeList scxmls = _doc.getElementsByTagName("scxml"); + if (scxmls.getLength() > 0) { + _scxml = (Arabica::DOM::Element)scxmls.item(0); + } else { + LOG(ERROR) << "Cannot find SCXML element" << std::endl; + } + _name = (HAS_ATTR(_scxml, "name") ? ATTR(_scxml, "name") : getUUID()); + } +} + +Interpreter::~Interpreter() { + std::map::iterator ioProcessorIter = _ioProcessors.begin(); + while(ioProcessorIter != _ioProcessors.end()) { + delete ioProcessorIter->second; + ioProcessorIter++; + } + if (_thread) { + _thread->join(); + delete(_thread); + } +} + +void Interpreter::start() { + _thread = new tthread::thread(Interpreter::run, this); +} + +void Interpreter::stop() { +} + +void Interpreter::run(void* instance) { + ((Interpreter*)instance)->interpret(); +} + +void Interpreter::waitForStabilization() { + tthread::lock_guard lock(_mutex); + _stabilized.wait(_mutex); +} + +// see: http://www.w3.org/TR/scxml/#AlgorithmforSCXMLInterpretation +void Interpreter::interpret() { + if (!_scxml) + return; +// dump(); + + _sessionId = getUUID(); + normalize(_doc); + + if(HAS_ATTR(_scxml, "datamodel")) { + _dataModel = Factory::getDataModel(ATTR(_scxml, "datamodel"), this); + if(_dataModel == NULL) { + LOG(ERROR) << "No datamodel for " << ATTR(_scxml, "datamodel") << " registered"; + return; + } + } + + setupIOProcessors(); + + // executeGlobalScriptElements + NodeSet globalScriptElems = _xpath.evaluate("/" + _nsPrefix + "scxml/" + _nsPrefix + "script", _doc).asNodeSet(); + for (unsigned int i = 0; i < globalScriptElems.size(); i++) { +// std::cout << globalScriptElems[i].getFirstChild().getNodeValue() << std::endl; + if (_dataModel) + executeContent(globalScriptElems[i]); + } + + _running = true; + + std::string binding = _xpath.evaluate("/" + _nsPrefix + "scxml/@binding", _doc).asString(); + _binding = (boost::iequals(binding, "late") ? LATE : EARLY); + + // initialize all data elements + if (_dataModel && _binding == EARLY) { + NodeSet dataElems = _xpath.evaluate("//" + _nsPrefix + "data", _doc).asNodeSet(); + for (unsigned int i = 0; i < dataElems.size(); i++) { + initializeData(dataElems[i]); + } + } else if(_dataModel) { + NodeSet topDataElems = _xpath.evaluate("/" + _nsPrefix + "scxml/" + _nsPrefix + "datamodel/" + _nsPrefix + "data", _doc).asNodeSet(); + for (unsigned int i = 0; i < topDataElems.size(); i++) { + initializeData(topDataElems[i]); + } + } + + // executeTransitionContent + Arabica::DOM::Element 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); + enterStates(initialTransitions); + + mainEventLoop(); +} + +void Interpreter::initializeData(const Arabica::DOM::Node& data) { + if (!_dataModel) { + LOG(ERROR) << "Cannot initialize data when no datamodel is given!"; + return; + } + try { + if (!HAS_ATTR(data, "id")) + return; + std::string value = "undefined"; + + if (HAS_ATTR(data, "expr")) { + value = ATTR(data, "expr"); + goto VALUE_INITIALIZED; + } + if (HAS_ATTR(data, "src")) { + Arabica::SAX::InputSourceResolver resolver(Arabica::SAX::InputSource(ATTR(data, "src")), + Arabica::default_string_adaptor()); + value = std::string(std::istreambuf_iterator(*resolver.resolve()), std::istreambuf_iterator()); + goto VALUE_INITIALIZED; + } + if (data.hasChildNodes()) { + // search for the text node with the actual script + if (data.getFirstChild().getNodeType() == Node_base::TEXT_NODE) { + value = data.getFirstChild().getNodeValue(); + } + } + +VALUE_INITIALIZED: + _dataModel->assign(ATTR(data, "id"), value); + + } catch (Event e) { + LOG(ERROR) << "Syntax error in send element:" << std::endl << e << std::endl; + } +} + +void Interpreter::normalize(const Arabica::DOM::Node& 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++) { + Arabica::DOM::Element stateElem = Arabica::DOM::Element(states[i]); + stateElem.setAttribute("isFirstEntry", "true"); + if (!stateElem.hasAttribute("id")) { + stateElem.setAttribute("id", getUUID()); + } + } + + Arabica::XPath::NodeSet finals = _xpath.evaluate("//" + _nsPrefix + "final", _doc).asNodeSet(); + for (int i = 0; i < finals.size(); i++) { + Arabica::DOM::Element finalElem = Arabica::DOM::Element(finals[i]); + finalElem.setAttribute("isFirstEntry", "true"); + if (!finalElem.hasAttribute("id")) { + finalElem.setAttribute("id", getUUID()); + } + } + + Arabica::XPath::NodeSet histories = _xpath.evaluate("//" + _nsPrefix + "history", _doc).asNodeSet(); + for (int i = 0; i < histories.size(); i++) { + Arabica::DOM::Element historyElem = Arabica::DOM::Element(histories[i]); + if (!historyElem.hasAttribute("id")) { + historyElem.setAttribute("id", getUUID()); + } + } + + Arabica::XPath::NodeSet scxml = _xpath.evaluate("/" + _nsPrefix + "scxml", _doc).asNodeSet(); + if (!((Arabica::DOM::Element)scxml[0]).hasAttribute("id")) { + ((Arabica::DOM::Element)scxml[0]).setAttribute("id", getUUID()); + } +} + +void Interpreter::mainEventLoop() { + while(_running) { + NodeSet enabledTransitions; + _stable = false; + + // Here we handle eventless transitions and transitions + // triggered by internal events until machine is stable + while(_running && !_stable) { +#if 0 + std::cout << "Configuration: "; + for (int i = 0; i < _configuration.size(); i++) { + std::cout << ((Arabica::DOM::Element)_configuration[i]).getAttribute("id") << ", "; + } + std::cout << std::endl; +#endif + enabledTransitions = selectEventlessTransitions(); + if (enabledTransitions.size() == 0) { + if (_internalQueue.size() == 0) { + _stable = true; + } else { + Event internalEvent = _internalQueue.front(); + _internalQueue.pop_front(); + _dataModel->setEvent(internalEvent); + enabledTransitions = selectTransitions(internalEvent.name); + } + } + if (!enabledTransitions.empty()) + microstep(enabledTransitions); + } + + for (unsigned int i = 0; i < _statesToInvoke.size(); i++) { + NodeSet invokes = _xpath.evaluate("" + _nsPrefix + "invoke", _statesToInvoke[i]).asNodeSet(); + for (unsigned int j = 0; j < invokes.size(); j++) { + // @TODO: + // boost::shared_ptr invoke = boost::static_pointer_cast(interpreter::Factory::create("invoke", invokes[j])); + // invoke->invoke(this); + } + } + + _statesToInvoke = NodeSet(); + if (!_internalQueue.empty()) + continue; + + { + tthread::lock_guard lock(_mutex); + _stabilized.notify_all(); + } + + Event externalEvent = _externalQueue.pop(); + if (!_running) + exitInterpreter(); + + if (_dataModel && boost::iequals(externalEvent.name, "cancel.invoke." + _sessionId)) + break; + + if (_dataModel) + _dataModel->setEvent(externalEvent); + for (unsigned int i = 0; i < _configuration.size(); i++) { + NodeSet invokes = _xpath.evaluate("" + _nsPrefix + "invoke", _configuration[i]).asNodeSet(); + for (unsigned int j = 0; j < invokes.size(); j++) { + std::string invokeId = ((Arabica::DOM::Element)invokes[i]).getAttribute("id"); + std::string autoForward = ((Arabica::DOM::Element)invokes[i]).getAttribute("autoforward"); + if (boost::iequals(invokeId, externalEvent.invokeid)) { + // @TODO + // boost::shared_ptr invoke = boost::static_pointer_cast(interpreter::Factory::create("invoke", invokes[j])); + // invoke->applyFinalize(this); + } + if (autoForward.length() > 0) { + send(invokeId, externalEvent); + } + } + } + enabledTransitions = selectTransitions(externalEvent.name); + if (!enabledTransitions.empty()) + microstep(enabledTransitions); + } + exitInterpreter(); +} + +void Interpreter::send(const std::string invokeId, Event& event) { +} + +void Interpreter::send(const Arabica::DOM::Node& element) { + SendRequest sendReq; + try { + // event + if (HAS_ATTR(element, "eventexpr") && _dataModel) { + sendReq.name = _dataModel->evalAsString(ATTR(element, "eventexpr")); + } else if (HAS_ATTR(element, "event")) { + sendReq.name = ATTR(element, "event"); + } else { + LOG(ERROR) << "send element does not feature event, or eventexpr without datamodel given"; + return; + } + // target + if (HAS_ATTR(element, "targetexpr") && _dataModel) { + sendReq.target = _dataModel->evalAsString(ATTR(element, "targetexpr")); + } else if (HAS_ATTR(element, "target")) { + sendReq.target = ATTR(element, "target"); + } + // type + if (HAS_ATTR(element, "typeexpr") && _dataModel) { + sendReq.type = _dataModel->evalAsString(ATTR(element, "typeexpr")); + } else if (HAS_ATTR(element, "type")) { + sendReq.type = ATTR(element, "type"); + } else { + sendReq.type = "http://www.w3.org/TR/scxml/#SCXMLEventProcessor"; + } + // id + if (HAS_ATTR(element, "idlocation") && _dataModel) { + sendReq.sendid = _dataModel->evalAsString(ATTR(element, "idlocation")); + } else if (HAS_ATTR(element, "id")) { + sendReq.sendid = ATTR(element, "id"); + } else { + /* + * The ids for and are subtly different. In a conformant + * SCXML document, they must be unique within the session, but in the case + * where the author does not provide them, the processor must generate a + * new unique ID not at load time but each time the element is executed. + * Furthermore the attribute 'idlocation' can be used to capture this + * automatically generated id. Finally note that the automatically generated + * id for has a special format. See 6.4.1 Attribute Details for + * details. The SCXML processor may generate all other ids in any format, + * as long as they are unique. + */ + sendReq.sendid = getUUID(); + } + /** @TODO: + * + * If 'idlocation' is present, the SCXML Processor must generate an id when + * the parent element is evaluated and store it in this location. + * See 3.14 IDs for details. + * + */ + + // delay + std::string delay; + sendReq.delayMs = 0; + if (HAS_ATTR(element, "delayexpr") && _dataModel) { + delay = _dataModel->evalAsString(ATTR(element, "delayexpr")); + } else if (HAS_ATTR(element, "delay")) { + delay = ATTR(element, "delay"); + } + if (delay.size() > 0) { + boost::trim(delay); + std::stringstream delayTime; + if (delay.size() > 2 && boost::iequals("ms", delay.substr(delay.length() - 2, 2))) { + delayTime << delay.substr(0, delay.size() - 2); + delayTime >> sendReq.delayMs; + } else if (delay.size() > 1 && boost::iequals("s", delay.substr(delay.length() - 1, 1))) { + delayTime << delay.substr(0, delay.size() - 1); + delayTime >> sendReq.delayMs; + sendReq.delayMs *= 1000; + } else { + LOG(ERROR) << "Cannot make sense of delay value " << delay << ": does not end in 's' or 'ms'"; + } + } + // namelist + if (HAS_ATTR(element, "namelist")) { + std::vector names = tokenizeIdRefs(ATTR(element, "namelist")); + for (int i = 0; i < names.size(); i++) { + sendReq.namelist[names[i]] = _dataModel->evalAsString(names[i]); + } + } + + // params + NodeSet params = _xpath.evaluate("" + _nsPrefix + "param", element).asNodeSet(); + for (int i = 0; i < params.size(); i++) { + if (!HAS_ATTR(params[i], "name")) { + LOG(ERROR) << "param element is missing name attribut"; + continue; + } + std::string paramValue; + if (HAS_ATTR(params[i], "expr") && _dataModel) { + std::string location = _dataModel->evalAsString(ATTR(params[i], "expr")); + paramValue = _dataModel->evalAsString(location); + } else if(HAS_ATTR(params[i], "location") && _dataModel) { + paramValue = _dataModel->evalAsString(ATTR(params[i], "location")); + } else { + LOG(ERROR) << "param element is missing expr or location or no datamodel is specified"; + continue; + } + sendReq.params[ATTR(params[i], "name")] = paramValue; + } + + // content + NodeSet contents = _xpath.evaluate("" + _nsPrefix + "content", element).asNodeSet(); + if (contents.size() > 1) + LOG(ERROR) << "Only a single content element is allowed for send elements - using first one"; + if (contents.size() > 0) { + if (HAS_ATTR(contents[0], "expr")) { + if (_dataModel) { + sendReq.content = _dataModel->evalAsString(ATTR(contents[0], "expr")); + } else { + LOG(ERROR) << "content element has expr attribute but no datamodel is specified."; + } + } else if (contents[0].hasChildNodes()) { +// dump(contents[0].getFirstChild()); + sendReq.content = contents[0].getFirstChild().getNodeValue(); + } else { + LOG(ERROR) << "content element does not specify any content."; + } + } + + IOProcessor* ioProc = getIOProcessor(sendReq.type); + if (ioProc != NULL) { + _ioProcessorsIds[sendReq.sendid] = ioProc; + ioProc->send(sendReq); + } + + } catch (Event e) { + LOG(ERROR) << "Syntax error in send element:" << std::endl << e << std::endl; + } +} + +void Interpreter::invoke(const Arabica::DOM::Node& element) { + InvokeRequest invokeReq; + try { + // type + if (HAS_ATTR(element, "typeexpr") && _dataModel) { + invokeReq.type = _dataModel->evalAsString(ATTR(element, "typeexpr")); + } else if (HAS_ATTR(element, "type")) { + invokeReq.type = ATTR(element, "type"); + } else { + LOG(ERROR) << "invoke element is missing expr or typeexpr or no datamodel is specified"; + } + + // src + if (HAS_ATTR(element, "srcexpr") && _dataModel) { + invokeReq.src = _dataModel->evalAsString(ATTR(element, "srcexpr")); + } else if (HAS_ATTR(element, "src")) { + invokeReq.src = ATTR(element, "src"); + } else { + LOG(ERROR) << "invoke element is missing src or srcexpr or no datamodel is specified"; + } + + // id + if (HAS_ATTR(element, "idlocation") && _dataModel) { + invokeReq.invokeid = _dataModel->evalAsString(ATTR(element, "idlocation")); + } else if (HAS_ATTR(element, "id")) { + invokeReq.invokeid = ATTR(element, "id"); + } else { + invokeReq.invokeid = getUUID(); + } + + // namelist + if (HAS_ATTR(element, "namelist")) { + invokeReq.namelist = ATTR(element, "namelist"); + } + + // autoforward + if (HAS_ATTR(element, "autoforward")) { + if (boost::iequals(ATTR(element, "autoforward"), "true")) { + invokeReq.autoForward = true; + } + } else { + invokeReq.autoForward = false; + } + + // params + NodeSet params = _xpath.evaluate("" + _nsPrefix + "param", element).asNodeSet(); + for (int i = 0; i < params.size(); i++) { + if (HAS_ATTR(params[i], "name")) { + LOG(ERROR) << "param element is missing name attribut"; + continue; + } + std::string paramValue; + if (HAS_ATTR(params[i], "expr") && _dataModel) { + paramValue = _dataModel->evalAsString(ATTR(params[i], "expr")); + } else if(HAS_ATTR(params[i], "location") && _dataModel) { + paramValue = _dataModel->evalAsString(ATTR(params[i], "location")); + } else { + LOG(ERROR) << "param element is missing expr or location or no datamodel is specified"; + continue; + } + invokeReq.params[ATTR(params[i], "name")] = paramValue; + } + + // content + NodeSet contents = _xpath.evaluate("" + _nsPrefix + "content", element).asNodeSet(); + if (contents.size() > 1) + LOG(ERROR) << "Only a single content element is allowed for send elements - using first one"; + if (contents.size() > 0) { + invokeReq.content = contents[0].getNodeValue(); + } + + IOProcessor* ioProc = getIOProcessor(invokeReq.type); + if (ioProc != NULL) { + _ioProcessorsIds[invokeReq.invokeid] = ioProc; + ioProc->invoke(invokeReq); + } + + } catch (Event e) { + LOG(ERROR) << "Syntax error in invoke element:" << std::endl << e << std::endl; + } + +} + +Arabica::XPath::NodeSet Interpreter::selectTransitions(const std::string& event) { + Arabica::XPath::NodeSet enabledTransitions; + + NodeSet atomicStates; + for (unsigned int i = 0; i < _configuration.size(); i++) { + if (isAtomic(_configuration[i])) + atomicStates.push_back(_configuration[i]); + } + atomicStates.to_document_order(); + + for (unsigned int i = 0; i < atomicStates.size(); i++) { + NodeSet ancestors = getProperAncestors(atomicStates[i], Arabica::DOM::Node()); + ancestors.push_back(atomicStates[i]); + for (unsigned int j = 0; j < ancestors.size(); j++) { + NodeSet transitions = _xpath.evaluate("" + _nsPrefix + "transition", ancestors[j]).asNodeSet(); + for (unsigned int k = 0; k < transitions.size(); k++) { + if (((Arabica::DOM::Element)transitions[k]).hasAttribute("event") && + nameMatch(((Arabica::DOM::Element)transitions[k]).getAttribute("event"), event) && + hasConditionMatch(transitions[k])) { + enabledTransitions.push_back(transitions[k]); + goto LOOP; + } + } + } + LOOP:; + } + return enabledTransitions; +} + +// see: http://www.w3.org/TR/scxml/#EventDescriptors +bool Interpreter::nameMatch(const std::string& transitionEvent, const std::string& event) { + assert(transitionEvent.size() > 0); + assert(event.size() > 0); + + // naive case of single descriptor and exact match + if (boost::equals(transitionEvent, event)) + return true; + + boost::char_separator sep(" "); + boost::tokenizer > tokens(transitionEvent, sep); + boost::tokenizer >::iterator tokenIter = tokens.begin(); + + while(tokenIter != tokens.end()) { + std::string eventDesc(*tokenIter++); + + // remove optional trailing .* for CCXML compatibility + if (eventDesc.find("*", eventDesc.size() - 1) != std::string::npos) + eventDesc = eventDesc.substr(0, eventDesc.size() - 1); + if (eventDesc.find(".", eventDesc.size() - 1) != std::string::npos) + eventDesc = eventDesc.substr(0, eventDesc.size() - 1); + + // are they already equal? + if (boost::equals(eventDesc, event)) + return true; + + // eventDesc has to be a real prefix of event now and therefore shorter + if (eventDesc.size() >= event.size()) + continue; + + // it is a prefix of the event name and event continues with .something + if (eventDesc.compare(event.substr(0, eventDesc.size())) == 0) + if (event.find(".", eventDesc.size()) == eventDesc.size()) + return true; + } + return false; +} + +Arabica::XPath::NodeSet Interpreter::selectEventlessTransitions() { + Arabica::XPath::NodeSet enabledTransitions; + + NodeSet atomicStates; + for (unsigned int i = 0; i < _configuration.size(); i++) { + if (isAtomic(_configuration[i])) + atomicStates.push_back(_configuration[i]); + } + atomicStates.to_document_order(); + + for (unsigned int i = 0; i < atomicStates.size(); i++) { + NodeSet ancestors = getProperAncestors(atomicStates[i], Arabica::DOM::Node()); + ancestors.push_back(atomicStates[i]); + for (unsigned int j = 0; j < ancestors.size(); j++) { + NodeSet transitions = _xpath.evaluate("" + _nsPrefix + "transition", ancestors[j]).asNodeSet(); + for (unsigned int k = 0; k < transitions.size(); k++) { + if (!((Arabica::DOM::Element)transitions[k]).hasAttribute("event") && hasConditionMatch(transitions[k])) { + enabledTransitions.push_back(transitions[k]); + goto LOOP; + } + } + } + LOOP:; + } + + enabledTransitions = filterPreempted(enabledTransitions); + return enabledTransitions; +} + +bool Interpreter::hasConditionMatch(const Arabica::DOM::Node& conditional) { + try { + if (_dataModel && HAS_ATTR(conditional, "cond")) + return _dataModel->evalAsBool(ATTR(conditional, "cond")); + } catch (Event e) { + LOG(ERROR) << "Syntax error in cond attribute of " << TAGNAME(conditional) << " element:" << std::endl << e << std::endl; + return false; + } + return true; // no condition is always true +} + +Arabica::XPath::NodeSet Interpreter::filterPreempted(const Arabica::XPath::NodeSet& enabledTransitions) { + Arabica::XPath::NodeSet filteredTransitions; + for (unsigned int i = 0; i < enabledTransitions.size(); i++) { + Arabica::DOM::Node t = enabledTransitions[i]; + for (unsigned int j = i+1; j < enabledTransitions.size(); j++) { + Arabica::DOM::Node t2 = enabledTransitions[j]; + if (isPreemptingTransition(t2, t)) + goto LOOP; + } + filteredTransitions.push_back(t); + LOOP:; + } + return filteredTransitions; +} + +bool Interpreter::isPreemptingTransition(const Arabica::DOM::Node& t1, const Arabica::DOM::Node& t2) { + if (t1 == t2) + return false; + if (isWithinSameChild(t1) && (!isTargetless(t2) && !isWithinSameChild(t2))) + return true; + if (!isTargetless(t1) && !isWithinSameChild(t1)) + return true; + return false; +} + +void Interpreter::microstep(const Arabica::XPath::NodeSet& enabledTransitions) { +#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; + NodeSet targetSet = getTargetStates(enabledTransitions[i]); + for (int j = 0; j < targetSet.size(); j++) { + std::cout << " " << ((Arabica::DOM::Element)targetSet[i]).getAttribute("id") << std::endl; + } + } + std::cout << std::endl; +#endif + + exitStates(enabledTransitions); + executeTransitionContent(enabledTransitions); + enterStates(enabledTransitions); +} + +void Interpreter::exitInterpreter() { + NodeSet statesToExit = _configuration; + statesToExit.to_document_order(); + statesToExit.reverse(); + + for (int i = 0; i < statesToExit.size(); i++) { + Arabica::XPath::NodeSet onExitElems = _xpath.evaluate("" + _nsPrefix + "onexit", statesToExit[i]).asNodeSet(); + for (int j = 0; j < onExitElems.size(); j++) { + executeContent(onExitElems[j]); + } + Arabica::XPath::NodeSet invokeElems = _xpath.evaluate("" + _nsPrefix + "invoke", statesToExit[i]).asNodeSet(); + for (int j = 0; j < invokeElems.size(); j++) { + cancelInvoke(invokeElems[j]); + } + if (isFinal(statesToExit[i]) && parentIsScxmlState(statesToExit[i])) { + returnDoneEvent(statesToExit[i]); + } + } + _configuration = NodeSet(); +} + +void Interpreter::executeTransitionContent(const Arabica::XPath::NodeSet& enabledTransitions) { + for (int i = 0; i < enabledTransitions.size(); i++) { + executeContent(enabledTransitions[i]); + } +} + +void Interpreter::executeContent(const NodeList& content) { + for (unsigned int i = 0; i < content.getLength(); i++) { + if (content.item(i).getNodeType() != Node_base::ELEMENT_NODE) + continue; + executeContent(content.item(i)); + } +} + +void Interpreter::executeContent(const Arabica::DOM::Node& content) { + if (content.getNodeType() != Node_base::ELEMENT_NODE) + return; + + if (false) { + } else if (boost::iequals(TAGNAME(content), "raise")) { + // --- RAISE -------------------------- + if (HAS_ATTR(content, "event")) { + Event event; + event.name = ATTR(content, "event"); + _internalQueue.push_back(event); + } + } else if (boost::iequals(TAGNAME(content), "if")) { + // --- IF / ELSEIF / ELSE -------------- + Arabica::DOM::Element ifElem = (Arabica::DOM::Element)content; + if(hasConditionMatch(ifElem)) { + // condition is true, execute all content up to an elseif, else or end + if (ifElem.hasChildNodes()) { + NodeList childs = ifElem.getChildNodes(); + for (unsigned int i = 0; i < childs.getLength(); i++) { + if (childs.item(i).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (boost::iequals(TAGNAME(childs.item(i)), "elsif") || + boost::iequals(TAGNAME(childs.item(i)), "else")) + break; + executeContent(childs.item(i)); + } + } + } else { + // condition does not match - do we have an elsif? + if (ifElem.hasChildNodes()) { + NodeList elseifElem = ifElem.getElementsByTagName("elseif"); + for (unsigned int i = 0; i < elseifElem.getLength(); i++) { + if (hasConditionMatch(elseifElem.item(i))) { + executeContent(elseifElem.item(i).getChildNodes()); + goto ELSIF_ELEM_MATCH; + } + } + NodeList elseElem = ifElem.getElementsByTagName("else"); + if (elseElem.getLength() > 0) + executeContent(elseElem.item(0).getChildNodes()); + } + } + ELSIF_ELEM_MATCH:; + } else if (boost::iequals(TAGNAME(content), "elseif")) { + std::cerr << "Found single elsif to evaluate!" << std::endl; + } else if (boost::iequals(TAGNAME(content), "else")) { + std::cerr << "Found single else to evaluate!" << std::endl; + } else if (boost::iequals(TAGNAME(content), "foreach")) { + // --- FOREACH -------------------------- + if (_dataModel) { + if (HAS_ATTR(content, "array") && HAS_ATTR(content, "item")) { + std::string array = ATTR(content, "array"); + std::string item = ATTR(content, "item"); + std::string index = (HAS_ATTR(content, "index") ? ATTR(content, "index") : ""); + uint32_t iterations = _dataModel->getLength(array); + _dataModel->pushContext(); // copy old and enter new context + for (uint32_t iteration = 0; iteration < iterations; iteration++) { + { + // assign array element to item + std::stringstream ss; + ss << array << "[" << iteration << "]"; + _dataModel->assign(item, ss.str()); + } + if (index.length() > 0) { + // assign iteration element to index + std::stringstream ss; + ss << iteration; + _dataModel->assign(index,ss.str()); + } + if (content.hasChildNodes()) + executeContent(content.getChildNodes()); + } + _dataModel->popContext(); // leave stacked context + } else { + LOG(ERROR) << "Expected array and item attributes with foreach element!" << std::endl; + } + } + } else if (boost::iequals(TAGNAME(content), "log")) { + // --- LOG -------------------------- + Arabica::DOM::Element logElem = (Arabica::DOM::Element)content; + if (logElem.hasAttribute("expr")) { + if (_dataModel) { + try { + std::cout << _dataModel->evalAsString(logElem.getAttribute("expr")) << std::endl; + } catch (Event e) { + LOG(ERROR) << "Syntax error in expr attribute of log element:" << std::endl << e << std::endl; + } + } else { + std::cout << logElem.getAttribute("expr") << std::endl; + } + } + } else if (boost::iequals(TAGNAME(content), "assign")) { + // --- ASSIGN -------------------------- + if (_dataModel && HAS_ATTR(content, "location") && HAS_ATTR(content, "expr")) { + try { + _dataModel->assign(ATTR(content, "location"), ATTR(content, "expr")); + } catch (Event e) { + LOG(ERROR) << "Syntax error in attributes of assign element:" << std::endl << e << std::endl; + } + } + } else if (boost::iequals(TAGNAME(content), "validate")) { + // --- VALIDATE -------------------------- + if (_dataModel) { + std::string location = (HAS_ATTR(content, "location") ? ATTR(content, "location") : ""); + std::string schema = (HAS_ATTR(content, "schema") ? ATTR(content, "schema") : ""); + _dataModel->validate(location, schema); + } + } else if (boost::iequals(TAGNAME(content), "script")) { + // --- SCRIPT -------------------------- + if (_dataModel) { + if (HAS_ATTR(content, "src")) { + Arabica::SAX::InputSourceResolver resolver(Arabica::SAX::InputSource(ATTR(content, "src")), + Arabica::default_string_adaptor()); + std::string srcContent(std::istreambuf_iterator(*resolver.resolve()), std::istreambuf_iterator()); + try { + _dataModel->eval(srcContent); + } catch (Event e) { + LOG(ERROR) << "Syntax error while executing script element from '" << ATTR(content, "src") << "':" << std::endl << e << std::endl; + } + } else { + if (content.hasChildNodes()) { + // search for the text node with the actual script + if (content.getFirstChild().getNodeType() == Node_base::TEXT_NODE) { + try { + _dataModel->eval(content.getFirstChild().getNodeValue()); + } catch (Event e) { + LOG(ERROR) << "Syntax error while executing script element" << std::endl << e << std::endl; + } + } + } + } + } + } else if (boost::iequals(TAGNAME(content), "send")) { + // --- SEND -------------------------- + send(content); + } else if (boost::iequals(TAGNAME(content), "cancel")) { + // --- CANCEL -------------------------- + std::string sendId; + try { + if (HAS_ATTR(content, "sendidexpr")) { + sendId = _dataModel->evalAsString(ATTR(content, "sendidexpr")); + } else if(HAS_ATTR(content, "sendid")) { + sendId = ATTR(content, "sendid"); + } else { + LOG(ERROR) << "Expected sendidexpr or sendid attribute in cancel element"; + return; + } + + IOProcessor* ioProc = getIOProcessorForId(sendId); + if (ioProc != NULL) { + ioProc->cancel(sendId); + } + + } catch (Event e) { + LOG(ERROR) << "Syntax error while executing cancel element" << std::endl << e << std::endl; + } + + } else if (boost::iequals(TAGNAME(content), "invoke")) { + // --- INVOKE -------------------------- + } else { + NodeList executable = content.getChildNodes(); + for (int i = 0; i < executable.getLength(); i++) { + executeContent(executable.item(i)); + } + } +} + +void Interpreter::cancelInvoke(const Arabica::DOM::Node& content) { +} + +void Interpreter::returnDoneEvent(const Arabica::DOM::Node& state) { +} + +void Interpreter::exitStates(const Arabica::XPath::NodeSet& enabledTransitions) { + NodeSet statesToExit; + for (int i = 0; i < enabledTransitions.size(); i++) { + Arabica::DOM::Element transition = ((Arabica::DOM::Element)enabledTransitions[i]); + if (!isTargetless(transition)) { + std::string transitionType = (boost::iequals(transition.getAttribute("type"), "internal") ? "internal" : "external"); + NodeSet tStates = getTargetStates(transition); + Arabica::DOM::Node ancestor; + Arabica::DOM::Node source = getSourceState(transition); + + bool allDescendants = true; + for (int j = 0; j < tStates.size(); j++) { + if (!isDescendant(tStates[j], source)) { + allDescendants = false; + break; + } + } + if (boost::iequals(transitionType, "internal") && + isCompound(source) && + allDescendants) + { + ancestor = source; + } else { + NodeSet tmpStates; + tmpStates.push_back(source); + tmpStates.insert(tmpStates.end(), tStates.begin(), tStates.end()); + + ancestor = findLCCA(tmpStates); + } + + for (int j = 0; j < _configuration.size(); j++) { + if (isDescendant(_configuration[j], ancestor)) + statesToExit.push_back(_configuration[j]); + } + } + } + // remove statesToExit from _statesToInvoke + std::list > tmp; + for (int i = 0; i < _statesToInvoke.size(); i++) { + if (!isMember(_statesToInvoke[i], statesToExit)) { + tmp.push_back(_statesToInvoke[i]); + } + } + _statesToInvoke = NodeSet(); + _statesToInvoke.insert(_statesToInvoke.end(), tmp.begin(), tmp.end()); + + statesToExit.to_document_order(); + statesToExit.reverse(); + + for (int i = 0; i < statesToExit.size(); i++) { + NodeSet historyElems = _xpath.evaluate("" + _nsPrefix + "history", statesToExit[i]).asNodeSet(); + for (int j = 0; j < historyElems.size(); j++) { + Arabica::DOM::Element historyElem = (Arabica::DOM::Element)historyElems[j]; + std::string historyType = (historyElem.hasAttribute("type") ? historyElem.getAttribute("type") : "shallow"); + NodeSet historyNodes; + for (int k = 0; k < _configuration.size(); k++) { + if (boost::iequals(historyType, "deep")) { + if (isAtomic(_configuration[k]) && isDescendant(_configuration[k], statesToExit[i])) + historyNodes.push_back(_configuration[k]); + } else { + if (_configuration[k].getParentNode() == statesToExit[i]) + historyNodes.push_back(_configuration[k]); + } + } + _historyValue[historyElem.getAttribute("id")] = historyNodes; + } + } + + for (int i = 0; i < statesToExit.size(); i++) { + Arabica::XPath::NodeSet onExitElems = _xpath.evaluate("" + _nsPrefix + "onexit", statesToExit[i]).asNodeSet(); + for (int j = 0; j < onExitElems.size(); j++) { + executeContent(onExitElems[j]); + } + Arabica::XPath::NodeSet invokeElems = _xpath.evaluate("" + _nsPrefix + "invoke", statesToExit[i]).asNodeSet(); + for (int j = 0; j < invokeElems.size(); j++) { + cancelInvoke(invokeElems[j]); + } + } + +// std::cout << "States to Exit: "; +// for (int i = 0; i < statesToExit.size(); i++) { +// std::cout << ((Arabica::DOM::Element)statesToExit[i]).getAttribute("id") << ", "; +// } +// std::cout << std::endl; + + // remove statesToExit from _configuration + tmp.clear(); + for (int i = 0; i < _configuration.size(); i++) { + if (!isMember(_configuration[i], statesToExit)) { + tmp.push_back(_configuration[i]); + } + } + _configuration = NodeSet(); + _configuration.insert(_configuration.end(), tmp.begin(), tmp.end()); + + +} + +void Interpreter::enterStates(const Arabica::XPath::NodeSet& enabledTransitions) { + NodeSet statesToEnter; + NodeSet statesForDefaultEntry; + + for (int i = 0; i < enabledTransitions.size(); i++) { + Arabica::DOM::Element transition = ((Arabica::DOM::Element)enabledTransitions[i]); + if (!isTargetless(transition)) { + std::string transitionType = (boost::iequals(transition.getAttribute("type"), "internal") ? "internal" : "external"); + NodeSet tStates = getTargetStates(transition); + Arabica::DOM::Node ancestor; + Arabica::DOM::Node source = getSourceState(transition); + assert(source); + + bool allDescendants = true; + for (int j = 0; j < tStates.size(); j++) { + if (!isDescendant(tStates[j], source)) { + allDescendants = false; + break; + } + } + if (boost::iequals(transitionType, "internal") && + isCompound(source) && + allDescendants) + { + ancestor = source; + } else { + NodeSet tmpStates; + tmpStates.push_back(source); + tmpStates.insert(tmpStates.end(), tStates.begin(), tStates.end()); + + ancestor = findLCCA(tmpStates); + } + + for (int j = 0; j < tStates.size(); j++) { + addStatesToEnter(tStates[j], statesToEnter, statesForDefaultEntry); + } + + for (int j = 0; j < tStates.size(); j++) { + NodeSet ancestors = getProperAncestors(tStates[j], ancestor); + for (int k = 0; k < ancestors.size(); k++) { + statesToEnter.push_back(ancestors[k]); + if(isParallel(ancestors[k])) { + NodeSet childs = getChildStates(ancestors[k]); + for (int l = 0; l < childs.size(); l++) { + bool someIsDescendant = false; + for (int m = 0; m < statesToEnter.size(); m++) { + if (isDescendant(statesToEnter[m], childs[l])) { + someIsDescendant = true; + break; + } + } + if (!someIsDescendant) { + addStatesToEnter(childs[l], statesToEnter, statesForDefaultEntry); + } + } + } + } + } + } + } + statesToEnter.to_document_order(); + for (int i = 0; i < statesToEnter.size(); i++) { + Arabica::DOM::Element stateElem = (Arabica::DOM::Element)statesToEnter[i]; + _configuration.push_back(stateElem); + _statesToInvoke.push_back(stateElem); + if (_binding == LATE && stateElem.getAttribute("isFirstEntry").size() > 0) { + Arabica::XPath::NodeSet dataModelElems = _xpath.evaluate("" + _nsPrefix + "datamodel", stateElem).asNodeSet(); + if(dataModelElems.size() > 0 && _dataModel) { + Arabica::XPath::NodeSet dataElems = _xpath.evaluate("" + _nsPrefix + "data", dataModelElems[0]).asNodeSet(); + for (int j = 0; j < dataElems.size(); j++) { + initializeData(dataElems[j]); + } + } + stateElem.setAttribute("isFirstEntry", ""); + } + // execute onentry executable content + Arabica::XPath::NodeSet onEntryElems = _xpath.evaluate("" + _nsPrefix + "onentry", stateElem).asNodeSet(); + for (int j = 0; j < onEntryElems.size(); j++) { + executeContent(onEntryElems[j]); + } + if (isMember(stateElem, statesForDefaultEntry)) { + // execute initial transition content for compund states + Arabica::XPath::NodeSet transitions = _xpath.evaluate("" + _nsPrefix + "initial/" + _nsPrefix + "transition", stateElem).asNodeSet(); + for (int j = 0; j < transitions.size(); j++) { + executeContent(transitions[j]); + } + } + + if (isFinal(stateElem)) { + Arabica::DOM::Element parent = (Arabica::DOM::Element)stateElem.getParentNode(); + + Event event; + event.name = "done.state." + parent.getAttribute("id"); + Arabica::XPath::NodeSet doneData = _xpath.evaluate("" + _nsPrefix + "donedata", stateElem).asNodeSet(); + if (doneData.size() > 0) { + event.dom = doneData[0]; + } + _internalQueue.push_back(event); + + if (isParallel(parent.getParentNode())) { + Arabica::DOM::Element grandParent = (Arabica::DOM::Element)parent.getParentNode(); + + Arabica::XPath::NodeSet childs = getChildStates(grandParent); + bool inFinalState = true; + for (int j = 0; j < childs.size(); j++) { + if (!isInFinalState(childs[j])) { + inFinalState = false; + break; + } + } + if (inFinalState) { + Event event; + event.name = "done.state." + grandParent.getAttribute("id"); + _internalQueue.push_back(event); + } + } + } + } + for (int i = 0; i < _configuration.size(); i++) { + Arabica::DOM::Element stateElem = (Arabica::DOM::Element)_configuration[i]; + if (isFinal(stateElem) && parentIsScxmlState(stateElem)) + _running = false; + } +} + +bool Interpreter::parentIsScxmlState(Arabica::DOM::Node state) { + Arabica::DOM::Element stateElem = (Arabica::DOM::Element)state; + Arabica::DOM::Element parentElem = (Arabica::DOM::Element)state.getParentNode(); + if (boost::iequals(parentElem.getTagName(), "scxml")) + return true; + return false; +} + +bool Interpreter::isInFinalState(const Arabica::DOM::Node& state) { + if (isCompound(state)) { + Arabica::XPath::NodeSet childs = getChildStates(state); + for (int i = 0; i < childs.size(); i++) { + if (isFinal(childs[i]) && isMember(childs[i], _configuration)) + return true; + } + } else if (isParallel(state)) { + Arabica::XPath::NodeSet childs = getChildStates(state); + for (int i = 0; i < childs.size(); i++) { + if (!isInFinalState(childs[i])) + return false; + } + return true; + } + return false; +} + +bool Interpreter::isMember(const Arabica::DOM::Node& node, const Arabica::XPath::NodeSet& set) { + for (int i = 0; i < set.size(); i++) { + if (set[i] == node) + return true; + } + return false; +} + +void Interpreter::addStatesToEnter(const Arabica::DOM::Node& state, + Arabica::XPath::NodeSet& statesToEnter, + Arabica::XPath::NodeSet& statesForDefaultEntry) { + std::string stateId = ((Arabica::DOM::Element)state).getAttribute("id"); + if (isHistory(state)) { + if (_historyValue.find(stateId) != _historyValue.end()) { + Arabica::XPath::NodeSet historyValue = _historyValue[stateId]; + for (int i = 0; i < historyValue.size(); i++) { + addStatesToEnter(historyValue[i], statesToEnter, statesForDefaultEntry); + NodeSet ancestors = getProperAncestors(historyValue[i], state); + for (int j = 0; j < ancestors.size(); j++) { + statesToEnter.push_back(ancestors[j]); + } + } + } else { + NodeSet transitions = _xpath.evaluate("" + _nsPrefix + "transition", state).asNodeSet(); + for (int i = 0; i < transitions.size(); i++) { + NodeSet targets = getTargetStates(transitions[i]); + for (int j = 0; j < targets.size(); j++) { + addStatesToEnter(targets[j], statesToEnter, statesForDefaultEntry); + } + } + } + } else { + statesToEnter.push_back(state); + if (isCompound(state)) { + statesForDefaultEntry.push_back(state); + NodeSet tStates = getTargetStates(getInitialState(state)); + for (int i = 0; i < tStates.size(); i++) { + addStatesToEnter(tStates[i], statesToEnter, statesForDefaultEntry); + } + } else if(isParallel(state)) { + NodeSet childStates = getChildStates(state); + for (int i = 0; i < childStates.size(); i++) { + addStatesToEnter(childStates[i], statesToEnter, statesForDefaultEntry); + } + } + } +} + +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(); + + childs.insert(childs.begin(), stateChilds.begin(), stateChilds.end()); + childs.insert(childs.begin(), parallelChilds.begin(), parallelChilds.end()); + + return childs; +} + +Arabica::DOM::Node Interpreter::findLCCA(const Arabica::XPath::NodeSet& states) { +// std::cout << "findLCCA: "; +// for (int i = 0; i < states.size(); i++) { +// std::cout << ((Arabica::DOM::Element)states[i]).getAttribute("id") << " - " << states[i].getLocalName() << ", "; +// } +// std::cout << std::flush; + + Arabica::XPath::NodeSet ancestors = getProperAncestors(states[0], Arabica::DOM::Node()); + ancestors.push_back(states[0]); // state[0] may already be the ancestor - bug in W3C spec? + Arabica::DOM::Node ancestor; + for (int i = 0; i < ancestors.size(); i++) { + for (int j = 0; j < states.size(); j++) { +// std::cout << "Checking " << TAGNAME(state) << " and " << TAGNAME(ancestors[i]) << std::endl; + if (!isDescendant(states[j], ancestors[i]) && (states[j] != ancestors[i])) + goto NEXT_ANCESTOR; + } + ancestor = ancestors[i]; + break; + NEXT_ANCESTOR:; + } + assert(ancestor); +// std::cout << " -> " << ((Arabica::DOM::Element)ancestor).getAttribute("id") << " " << ancestor.getLocalName() << std::endl; + return ancestor; +} + +Arabica::DOM::Node Interpreter::getState(const std::string& stateId) { + // first try atomic and compund states + NodeSet target = _xpath.evaluate("//" + _nsPrefix + "state[@id='" + stateId + "']", _doc).asNodeSet(); + if (target.size() > 0) + goto FOUND; + + // now parallel states + target = _xpath.evaluate("//" + _nsPrefix + "parallel[@id='" + stateId + "']", _doc).asNodeSet(); + if (target.size() > 0) + goto FOUND; + + // now final states + target = _xpath.evaluate("//" + _nsPrefix + "final[@id='" + stateId + "']", _doc).asNodeSet(); + if (target.size() > 0) + goto FOUND; + +FOUND: + if (target.size() > 0) { + assert(target.size() == 1); + return target[0]; + } + // return the empty node + return Arabica::DOM::Node(); +} + +Arabica::DOM::Node Interpreter::getSourceState(const Arabica::DOM::Node& transition) { + if (boost::iequals(TAGNAME(transition.getParentNode()), "initial")) + return transition.getParentNode().getParentNode(); + return transition.getParentNode(); +} + + /** + * In a conformant SCXML document, a compound state may specify either an "initial" + * attribute or an element, but not both. See 3.6 for a + * discussion of the difference between the two notations. If neither the "initial" + * attribute nor an element is specified, the SCXML Processor must use + * the first child state in document order as the default initial state. + */ +Arabica::DOM::Node Interpreter::getInitialState(Arabica::DOM::Node state) { + if (!state) { + state = _doc.getFirstChild(); + while(!isState(state)) + state = state.getNextSibling(); + } + + assert(isCompound(state) || isParallel(state)); + + // initial attribute at element + Arabica::DOM::Element stateElem = (Arabica::DOM::Element)state; + if (stateElem.hasAttribute("initial")) { + return getState(stateElem.getAttribute("initial")); + } + + // initial element as child + NodeSet initialStates = _xpath.evaluate("" + _nsPrefix + "initial", state).asNodeSet(); + if(initialStates.size() == 1) + return initialStates[0]; + + // first child state + NodeList childs = state.getChildNodes(); + for (int i = 0; i < childs.getLength(); i++) { + if (isState(childs.item(i))) + return childs.item(i); + } + // nothing found + return Arabica::DOM::Node(); +} + +NodeSet Interpreter::getTargetStates(const Arabica::DOM::Node& transition) { + NodeSet targetStates; + std::string targetId = ((Arabica::DOM::Element)transition).getAttribute("target"); + + std::vector targetIds = Interpreter::tokenizeIdRefs(ATTR(transition, "target")); + for (int i = 0; i < targetIds.size(); i++) { + targetStates.push_back(getState(targetIds[i])); + } + return targetStates; +} + +std::vector Interpreter::tokenizeIdRefs(const std::string& idRefs) { + std::vector ids; + + if (idRefs.length() > 0) { + std::istringstream iss(idRefs); + + std::copy(std::istream_iterator(iss), + std::istream_iterator(), + std::back_inserter >(ids)); + } + + return ids; +} + +NodeSet Interpreter::getProperAncestors(const Arabica::DOM::Node& s1, + const Arabica::DOM::Node& s2) { + NodeSet ancestors; + if (isState(s1)) { + Arabica::DOM::Node node = s1; + while((node = node.getParentNode())) { + if (!isState(node)) + break; + if (!boost::iequals(TAGNAME(node), "parallel") && !boost::iequals(TAGNAME(node), "state") && !boost::iequals(TAGNAME(node), "scxml")) + break; + if (node == s2) + break; + ancestors.push_back(node); + } + } + return ancestors; +} + +bool Interpreter::isDescendant(const Arabica::DOM::Node& s1, + const Arabica::DOM::Node& s2) { + Arabica::DOM::Node parent = s1.getParentNode(); + while(parent) { + if (s2 == parent) + return true; + parent = parent.getParentNode(); + } + return false; +} + +bool Interpreter::isTargetless(const Arabica::DOM::Node& transition) { + if (transition.hasAttributes()) { + if (((Arabica::DOM::Element)transition).hasAttribute("target")) + return false; + } + return true; +} + +bool Interpreter::isWithinSameChild(const Arabica::DOM::Node& transition) { + if (!isTargetless(transition)) { + std::string target = ((Arabica::DOM::Element)transition).getAttribute("target"); + Arabica::XPath::XPath xpath; + // @todo: do we need to look at parallel as well? + if (xpath.evaluate("" + _nsPrefix + "state[id=\"" + target + "\"]", transition.getParentNode()).asNodeSet().size() > 0) + return true; + } + return false; +} + +bool Interpreter::isState(const Arabica::DOM::Node& state) { + if (!state) + return false; + if (state.getNodeType() != Arabica::DOM::Node_base::ELEMENT_NODE) + return false; + + std::string tagName = TAGNAME(state); + if (boost::iequals("state", tagName)) + return true; + if (boost::iequals("scxml", tagName)) + return true; + if (boost::iequals("parallel", tagName)) + return true; + if (boost::iequals("final", tagName)) + return true; + return false; +} + +bool Interpreter::isFinal(const Arabica::DOM::Node& state) { + std::string tagName = TAGNAME(state); + if (boost::iequals("final", tagName)) + return true; + if (HAS_ATTR(state, "final") && boost::iequals("true", ATTR(state, "final"))) + return true; + return false; +} + +bool Interpreter::isPseudoState(const Arabica::DOM::Node& state) { + std::string tagName = TAGNAME(state); + if (boost::iequals("initial", tagName)) + return true; + if (boost::iequals("history", tagName)) + return true; + return false; +} + +bool Interpreter::isTransitionTarget(const Arabica::DOM::Node& elem) { + return (isState(elem) || boost::iequals(TAGNAME(elem), "history")); +} + +bool Interpreter::isAtomic(const Arabica::DOM::Node& state) { + if (boost::iequals("final", TAGNAME(state))) + return true; + + // I will assume that parallel states are not meant to be atomic. + if (boost::iequals("parallel", TAGNAME(state))) + return false; + + Arabica::DOM::NodeList childs = state.getChildNodes(); + for (unsigned int i = 0; i < childs.getLength(); i++) { + if (isState(childs.item(i))) + return false; + } + return true; +} + +bool Interpreter::isHistory(const Arabica::DOM::Node& state) { + if (boost::iequals("history", TAGNAME(state))) + return true; + return false; +} + +bool Interpreter::isParallel(const Arabica::DOM::Node& state) { + if (!isState(state)) + return false; + if (boost::iequals("parallel", TAGNAME(state))) + return true; + return false; +} + + +bool Interpreter::isCompound(const Arabica::DOM::Node& state) { + if (!isState(state)) + return false; + + if (boost::iequals(TAGNAME(state), "parallel")) + return false; + + Arabica::DOM::NodeList childs = state.getChildNodes(); + for (unsigned int i = 0; i < childs.getLength(); i++) { + if (isState(childs.item(i))) + return true; + } + return false; +} + +void Interpreter::setupIOProcessors() { + std::map::iterator ioProcIter = Factory::getInstance()->_ioProcessors.begin(); + while(ioProcIter != Factory::getInstance()->_ioProcessors.end()) { + _ioProcessors[ioProcIter->first] = Factory::getIOProcessor(ioProcIter->first, this); + if (_dataModel) { + try { + _dataModel->assign("_ioprocessors['" + ioProcIter->first + "']", "'" + _ioProcessors[ioProcIter->first]->getURL() + "'"); + } catch (Event e) { + LOG(ERROR) << "Syntax error when setting _ioprocessors:" << std::endl << e << std::endl; + } + } else { + LOG(INFO) << "Not registering " << ioProcIter->first << " at _ioprocessors in datamodel, no datamodel specified"; + } + ioProcIter++; + } +} + +IOProcessor* Interpreter::getIOProcessor(const std::string& type) { + if (_ioProcessors.find(type) == _ioProcessors.end()) { + LOG(ERROR) << "No ioProcessor known for type " << type; + return NULL; + } + return _ioProcessors[type]; +} + +IOProcessor* Interpreter::getIOProcessorForId(const std::string& sendId) { + if (_ioProcessorsIds.find(sendId) == _ioProcessorsIds.end()) { + LOG(ERROR) << "No ioProcessor with pending send id " << sendId << sendId; + return NULL; + } + return _ioProcessorsIds[sendId]; +} + +bool Interpreter::validate() { + bool validationErrors = false; + + if (!_doc) { + LOG(ERROR) << "Document " << _url << " was not parsed successfully" << std::endl; + return false; + } + + // semantic issues ------------ + if ((_xpath.evaluate("/" + _nsPrefix + "scxml/@datamodel", _doc).asNodeSet().size() == 0) && + _xpath.evaluate("/" + _nsPrefix + "scxml/" + _nsPrefix + "script", _doc).asNodeSet().size() > 0) { + LOG(ERROR) << "Script elements used, but no datamodel specified" << std::endl; + } + + // element issues ------------ + Arabica::XPath::NodeSet scxmlElems = _xpath.evaluate(_nsPrefix + "scxml", _doc).asNodeSet(); + if (scxmlElems.size() > 0) + LOG(ERROR) << "More than one scxml element found" << std::endl; + for (unsigned int i = 0; i < scxmlElems.size(); i++) { + if (!HAS_ATTR(scxmlElems[i], "xmlns")) + LOG(ERROR) << "scxml element has no xmlns attribute" << std::endl; + if (!HAS_ATTR(scxmlElems[i], "version")) + LOG(ERROR) << "scxml element has no version attribute" << std::endl; + NodeList childs = scxmlElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (boost::iequals(TAGNAME(childs.item(j)), "state") || + boost::iequals(TAGNAME(childs.item(j)), "parallel") || + boost::iequals(TAGNAME(childs.item(j)), "final") || + boost::iequals(TAGNAME(childs.item(j)), "datamodel") || + boost::iequals(TAGNAME(childs.item(j)), "script")) { + LOG(ERROR) << "scxml element has unspecified child " << TAGNAME(childs.item(j)) << std::endl; + } + } + } + + Arabica::XPath::NodeSet stateElems = _xpath.evaluate(_nsPrefix + "state", _doc).asNodeSet(); + for (unsigned int i = 0; i < stateElems.size(); i++) { + NodeList childs = stateElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (boost::iequals(TAGNAME(childs.item(j)), "onentry") || + boost::iequals(TAGNAME(childs.item(j)), "onexit") || + boost::iequals(TAGNAME(childs.item(j)), "transition") || + boost::iequals(TAGNAME(childs.item(j)), "initial") || + boost::iequals(TAGNAME(childs.item(j)), "state") || + boost::iequals(TAGNAME(childs.item(j)), "parallel") || + boost::iequals(TAGNAME(childs.item(j)), "final") || + boost::iequals(TAGNAME(childs.item(j)), "history") || + boost::iequals(TAGNAME(childs.item(j)), "datamodel") || + boost::iequals(TAGNAME(childs.item(j)), "invoke")) { + LOG(ERROR) << "state element has unspecified child " << TAGNAME(childs.item(j)) << std::endl; + } + } + } + + Arabica::XPath::NodeSet parallelElems = _xpath.evaluate(_nsPrefix + "parallel", _doc).asNodeSet(); + for (unsigned int i = 0; i < parallelElems.size(); i++) { + NodeList childs = parallelElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (boost::iequals(TAGNAME(childs.item(j)), "onentry") || + boost::iequals(TAGNAME(childs.item(j)), "onexit") || + boost::iequals(TAGNAME(childs.item(j)), "transition") || + boost::iequals(TAGNAME(childs.item(j)), "state") || + boost::iequals(TAGNAME(childs.item(j)), "parallel") || + boost::iequals(TAGNAME(childs.item(j)), "history") || + boost::iequals(TAGNAME(childs.item(j)), "datamodel") || + boost::iequals(TAGNAME(childs.item(j)), "invoke")) { + LOG(ERROR) << "parallel element has unspecified child " << TAGNAME(childs.item(j)) << std::endl; + } + } + } + + Arabica::XPath::NodeSet transitionElems = _xpath.evaluate(_nsPrefix + "transition", _doc).asNodeSet(); + for (unsigned int i = 0; i < transitionElems.size(); i++) { + if (HAS_ATTR(transitionElems[i], "cond") && + !HAS_ATTR(transitionElems[i], "event")) { + LOG(ERROR) << "transition element with cond attribute but without event attribute not allowed" << std::endl; + } + NodeList childs = scxmlElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + } + } + + Arabica::XPath::NodeSet initialElems = _xpath.evaluate(_nsPrefix + "initial", _doc).asNodeSet(); + for (unsigned int i = 0; i < initialElems.size(); i++) { + NodeList childs = initialElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (boost::iequals(TAGNAME(childs.item(j)), "transition")) { + LOG(ERROR) << "initial element has unspecified child " << TAGNAME(childs.item(j)) << std::endl; + } + } + } + + Arabica::XPath::NodeSet finalElems = _xpath.evaluate(_nsPrefix + "final", _doc).asNodeSet(); + for (unsigned int i = 0; i < finalElems.size(); i++) { + NodeList childs = finalElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (!boost::iequals(TAGNAME(childs.item(j)), "onentry") || + !boost::iequals(TAGNAME(childs.item(j)), "onexit") || + !boost::iequals(TAGNAME(childs.item(j)), "donedata")) { + LOG(ERROR) << "parallel element has unspecified child " << TAGNAME(childs.item(j)) << std::endl; + } + } + } + + Arabica::XPath::NodeSet historyElems = _xpath.evaluate(_nsPrefix + "history", _doc).asNodeSet(); + for (unsigned int i = 0; i < historyElems.size(); i++) { + NodeList childs = historyElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (boost::iequals(TAGNAME(childs.item(j)), "transition")) { + LOG(ERROR) << "history element has unspecified child " << TAGNAME(childs.item(j)) << std::endl; + } + } + } + + Arabica::XPath::NodeSet datamodelElems = _xpath.evaluate(_nsPrefix + "datamodel", _doc).asNodeSet(); + for (unsigned int i = 0; i < datamodelElems.size(); i++) { + NodeList childs = datamodelElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (!boost::iequals(TAGNAME(childs.item(j)), "data")) { + LOG(ERROR) << "datamodel element has unspecified child " << TAGNAME(childs.item(j)) << std::endl; + } + } + } + + Arabica::XPath::NodeSet dataElems = _xpath.evaluate(_nsPrefix + "data", _doc).asNodeSet(); + for (unsigned int i = 0; i < dataElems.size(); i++) { + if (!HAS_ATTR(dataElems[i], "id")) + LOG(ERROR) << "data element has no id attribute" << std::endl; + } + + return validationErrors; +} + +void Interpreter::dump() { + if (!_doc) + return; + dump(_doc); +} + +void Interpreter::dump(const Arabica::DOM::Node& node, int lvl) { + if (!node) + return; + + std::string indent = ""; + for (unsigned int i = 0; i < lvl; i++) + indent += " "; + + std::cout << indent; + switch(node.getNodeType()) { + case Arabica::DOM::Node_base::ELEMENT_NODE: { + std::cout << "ELEMENT_NODE: "; + Arabica::DOM::Element elem = (Arabica::DOM::Element)node; + break; + } + case Arabica::DOM::Node_base::ATTRIBUTE_NODE: + std::cout << "ATTRIBUTE_NODE: "; + break; + case Arabica::DOM::Node_base::TEXT_NODE: + std::cout << "TEXT_NODE: "; + break; + case Arabica::DOM::Node_base::CDATA_SECTION_NODE: + std::cout << "CDATA_SECTION_NODE: "; + break; + case Arabica::DOM::Node_base::ENTITY_REFERENCE_NODE: + std::cout << "ENTITY_REFERENCE_NODE: "; + break; + case Arabica::DOM::Node_base::ENTITY_NODE: + std::cout << "ENTITY_NODE: "; + break; + case Arabica::DOM::Node_base::PROCESSING_INSTRUCTION_NODE: + std::cout << "PROCESSING_INSTRUCTION_NODE: "; + break; + case Arabica::DOM::Node_base::COMMENT_NODE: + std::cout << "COMMENT_NODE: "; + break; + case Arabica::DOM::Node_base::DOCUMENT_NODE: + std::cout << "DOCUMENT_NODE: "; + break; + case Arabica::DOM::Node_base::DOCUMENT_TYPE_NODE: + std::cout << "DOCUMENT_TYPE_NODE: "; + break; + case Arabica::DOM::Node_base::DOCUMENT_FRAGMENT_NODE: + std::cout << "DOCUMENT_FRAGMENT_NODE: "; + break; + case Arabica::DOM::Node_base::NOTATION_NODE: + std::cout << "NOTATION_NODE: "; + break; + case Arabica::DOM::Node_base::MAX_TYPE: + std::cout << "MAX_TYPE: "; + break; + } + std::cout << node.getNamespaceURI() << " " << node.getNodeName() << std::endl; + + if (node.getNodeValue().length() > 0 && node.getNodeValue().find_first_not_of(" \t\n") != std::string::npos) + std::cout << indent << "Value: '" << node.getNodeValue() << "'" << std::endl; + + + if (node.hasAttributes()) { + Arabica::DOM::NamedNodeMap attrs = node.getAttributes(); + for (unsigned int i = 0; i < attrs.getLength(); i++) { + std::cout << indent << " " << attrs.item(i).getLocalName() << " = " << attrs.item(i).getNodeValue() << " (" << std::endl; + std::cout << indent << " namespace: " << attrs.item(i).getNamespaceURI() << std::endl; + std::cout << indent << " nodeName: " << attrs.item(i).getNodeName() << std::endl; + std::cout << indent << " prefix: " << attrs.item(i).getPrefix() << std::endl; + std::cout << indent << " )" << std::endl; + } + } + + if (node.hasChildNodes()) { + Arabica::DOM::NodeList childs = node.getChildNodes(); + for (unsigned int i = 0; i < childs.getLength(); i++) { + dump(childs.item(i), lvl+1); + } + } +} + +} \ No newline at end of file diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h new file mode 100644 index 0000000..fb913d1 --- /dev/null +++ b/src/uscxml/Interpreter.h @@ -0,0 +1,314 @@ +#ifndef RUNTIME_H_SQ1MBKGN +#define RUNTIME_H_SQ1MBKGN + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include "uscxml/concurrency/tinythread.h" +#include "uscxml/concurrency/BlockingQueue.h" +#include "uscxml/Message.h" +#include "uscxml/Factory.h" + +namespace uscxml { + + class Interpreter { + public: + enum Binding { + EARLY = 0, + LATE = 1 + }; + + Interpreter(const std::string& url); + virtual ~Interpreter(); + + void start(); + void stop(); + static void run(void*); + + void interpret(); + bool validate(); + + void waitForStabilization(); + + void receive(Event& event) { _externalQueue.push(event); } + void receiveInternal(Event& event) { _internalQueue.push_back(event); } + Arabica::XPath::NodeSet getConfiguration() { return _configuration; } + Arabica::DOM::Node getState(const std::string& stateId); + + const std::string& getName() { return _name; } + const std::string& getSessionId() { return _sessionId; } + + static bool isMember(const Arabica::DOM::Node& node, const Arabica::XPath::NodeSet& set); + + void dump(); + static void dump(const Arabica::DOM::Node& node, int lvl = 0); + + protected: + void normalize(const Arabica::DOM::Node& node); + void setupIOProcessors(); + + void mainEventLoop(); + + bool _stable; + tthread::thread* _thread; + tthread::mutex _mutex; + tthread::condition_variable _stabilized; + + std::string _url; + Arabica::DOM::Document _doc; + Arabica::DOM::Element _scxml; + Arabica::XPath::XPath _xpath; + Arabica::XPath::StandardNamespaceContext _nsContext; + std::string _nsPrefix; + + bool _running; + Binding _binding; + Arabica::XPath::NodeSet _configuration; + Arabica::XPath::NodeSet _statesToInvoke; + + DataModel* _dataModel; + std::map > _historyValue; + + std::list _internalQueue; + uscxml::concurrency::BlockingQueue _externalQueue; + + void microstep(const Arabica::XPath::NodeSet& enabledTransitions); + void exitStates(const Arabica::XPath::NodeSet& enabledTransitions); + void enterStates(const Arabica::XPath::NodeSet& enabledTransitions); + void executeTransitionContent(const Arabica::XPath::NodeSet& enabledTransitions); + void executeContent(const Arabica::DOM::Node& content); + void executeContent(const Arabica::DOM::NodeList& content); + void initializeData(const Arabica::DOM::Node& data); + void exitInterpreter(); + + void addStatesToEnter(const Arabica::DOM::Node& state, + Arabica::XPath::NodeSet& statesToEnter, + Arabica::XPath::NodeSet& statesForDefaultEntry); + + 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); + + + void send(const std::string invokeId, Event& event); + void send(const Arabica::DOM::Node& element); + void invoke(const Arabica::DOM::Node& element); + void cancelInvoke(const Arabica::DOM::Node& content); + void returnDoneEvent(const Arabica::DOM::Node& state); + + static bool nameMatch(const std::string& transitionEvent, const std::string& event); + Arabica::XPath::NodeSet filterPreempted(const Arabica::XPath::NodeSet& enabledTransitions); + bool hasConditionMatch(const Arabica::DOM::Node& conditional); + bool isPreemptingTransition(const Arabica::DOM::Node& t1, const Arabica::DOM::Node& t2); + 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; + static const std::string getUUID(); + + std::string _name; + std::string _sessionId; + + IOProcessor* getIOProcessor(const std::string& type); + IOProcessor* getIOProcessorForId(const std::string& sendId); + + std::map _ioProcessorsIds; + std::map _ioProcessors; + + }; + +#if 0 + class SCXMLParseHandler : + public Arabica::SAX::EntityResolver, + public Arabica::SAX::DTDHandler, + public Arabica::SAX::ContentHandler, + public Arabica::SAX::CatchErrorHandler, + public Arabica::SAX::LexicalHandler, + public Arabica::SAX::DeclHandler + { + public: + SCXMLParseHandler() { } + virtual ~SCXMLParseHandler() { } + + // EntityResolver + virtual InputSourceT resolveEntity(const std::string& publicId , const std::string& systemId) { + return InputSourceT(); + } + + // DTDHandler + virtual void notationDecl(const std::string& name, + const std::string& publicId, + const std::string& systemId) { + std::cout << "notationDecl" << std::endl; + std::cout << " name:" << name << std::endl; + std::cout << " publicId:" << publicId << std::endl; + std::cout << " systemId:" << systemId << std::endl; + } + virtual void unparsedEntityDecl(const std::string& name, + const std::string& publicId, + const std::string& systemId, + const std::string& notationName) { + std::cout << "unparsedEntityDecl" << std::endl; + std::cout << " name:" << name << std::endl; + std::cout << " publicId:" << publicId << std::endl; + std::cout << " systemId:" << systemId << std::endl; + std::cout << " notationName:" << notationName << std::endl; + } + + // ContentHandler + virtual void setDocumentLocator(const LocatorT& locator) { + std::cout << "setDocumentLocator" << std::endl; + } + virtual void startDocument() { + std::cout << "startDocument" << std::endl; + } + virtual void endDocument() { + std::cout << "endDocument" << std::endl; + } + virtual void startPrefixMapping(const std::string& prefix, const std::string& uri) { + std::cout << "startPrefixMapping" << std::endl; + std::cout << " prefix:" << prefix << std::endl; + std::cout << " uri:" << uri << std::endl; + } + virtual void endPrefixMapping(const std::string& prefix) { + std::cout << "endPrefixMapping" << std::endl; + std::cout << " prefix:" << prefix << std::endl; + } + virtual void startElement(const std::string& namespaceURI, const std::string& localName, + const std::string& qName, const AttributesT& atts) { + std::cout << "startElement" << std::endl; + std::cout << " namespaceURI:" << namespaceURI << std::endl; + std::cout << " localName:" << localName << std::endl; + std::cout << " qName:" << qName << std::endl; + std::cout << " atts:" << atts.getLength() << std::endl; + } + virtual void endElement(const std::string& namespaceURI, const std::string& localName, + const std::string& qName) { + std::cout << "endElement" << std::endl; + std::cout << " namespaceURI:" << namespaceURI << std::endl; + std::cout << " localName:" << localName << std::endl; + std::cout << " qName:" << qName << std::endl; + } + virtual void characters(const std::string& ch) { + std::cout << "characters" << std::endl; + std::cout << " ch:" << ch << std::endl; + } + virtual void ignorableWhitespace(const std::string& ch) { + std::cout << "ignorableWhitespace" << std::endl; + std::cout << " ch:" << ch << std::endl; + } + virtual void processingInstruction(const std::string& target, const std::string& data) { + std::cout << "processingInstruction" << std::endl; + std::cout << " target:" << target << std::endl; + std::cout << " data:" << data << std::endl; + } + virtual void skippedEntity(const std::string& name) { + std::cout << "skippedEntity" << std::endl; + std::cout << " name:" << name << std::endl; + } + + // ErrorHandler + virtual void warning(const SAXParseExceptionT& e) { Arabica::SAX::CatchErrorHandler::warning(e); } + virtual void error(const SAXParseExceptionT& e) { Arabica::SAX::CatchErrorHandler::error(e); } + virtual void fatalError(const SAXParseExceptionT& e) { + Arabica::SAX::CatchErrorHandler::fatalError(e); + } + + // LexicalHandler + virtual void startDTD(const std::string& name, + const std::string& publicId, + const std::string& systemId) { + std::cout << "startDTD" << std::endl; + std::cout << " name:" << name << std::endl; + std::cout << " publicId:" << publicId << std::endl; + std::cout << " systemId:" << systemId << std::endl; + } + + virtual void endDTD() { + std::cout << "endDTD" << std::endl; + } + virtual void startEntity(const std::string& name) { + std::cout << "startEntity" << std::endl; + std::cout << " name:" << name << std::endl; + } + virtual void endEntity(const std::string& name) { + std::cout << "endEntity" << std::endl; + std::cout << " name:" << name << std::endl; + } + virtual void startCDATA() { + std::cout << "startCDATA" << std::endl; + } + virtual void endCDATA() { + std::cout << "endCDATA" << std::endl; + } + virtual void comment(const std::string& text) { + std::cout << "comment" << std::endl; + std::cout << " text:" << text << std::endl; + } + + // DeclHandler + virtual void elementDecl(const std::string& name, const std::string& model) { + std::cout << "elementDecl" << std::endl; + std::cout << " name:" << name << std::endl; + std::cout << " model:" << model << std::endl; + } + virtual void attributeDecl(const std::string& elementName, + const std::string& attributeName, + const std::string& type, + const std::string& valueDefault, + const std::string& value) { + std::cout << "attributeDecl" << std::endl; + std::cout << " elementName:" << elementName << std::endl; + std::cout << " attributeName:" << attributeName << std::endl; + std::cout << " type:" << type << std::endl; + std::cout << " valueDefault:" << valueDefault << std::endl; + std::cout << " value:" << value << std::endl; + } + virtual void internalEntityDecl(const std::string& name, const std::string& value) { + std::cout << "internalEntityDecl" << std::endl; + std::cout << " name:" << name << std::endl; + std::cout << " value:" << value << std::endl; + } + virtual void externalEntityDecl(const std::string& name, + const std::string& publicId, + const std::string& systemId) { + std::cout << "externalEntityDecl" << std::endl; + std::cout << " name:" << name << std::endl; + std::cout << " publicId:" << publicId << std::endl; + std::cout << " systemId:" << systemId << std::endl; + } + + }; +#endif +} + +#endif diff --git a/src/uscxml/Message.cpp b/src/uscxml/Message.cpp new file mode 100644 index 0000000..3d3048b --- /dev/null +++ b/src/uscxml/Message.cpp @@ -0,0 +1,168 @@ +#include "uscxml/Message.h" +#include +#include + +namespace uscxml { + +static int _dataIndentation = 1; + + +Arabica::DOM::Document Data::toDocument() { + Arabica::DOM::DOMImplementation domFactory = Arabica::SimpleDOM::DOMImplementation::getDOMImplementation(); + Arabica::DOM::Document document = domFactory.createDocument("http://www.w3.org/2005/07/scxml", "message", 0); + Arabica::DOM::Element scxmlMsg = document.getDocumentElement(); + scxmlMsg.setPrefix("scxml"); + scxmlMsg.setAttribute("version", "1.0"); + + if (compound.size() > 0 || array.size() > 0) { + Arabica::DOM::Element payloadElem = document.createElementNS("http://www.w3.org/2005/07/scxml", "scxml:payload"); + scxmlMsg.appendChild(payloadElem); + + // we do not support nested attibutes + if (compound.size() > 0) { + std::map::iterator compoundIter = compound.begin(); + while(compoundIter != compound.end()) { + if (compoundIter->second.atom.size() > 0) { + Arabica::DOM::Element propertyElem = document.createElementNS("http://www.w3.org/2005/07/scxml", "scxml:property"); + propertyElem.setAttribute("name", compoundIter->first); + Arabica::DOM::Text textElem = document.createTextNode(compoundIter->second.atom); + propertyElem.appendChild(textElem); + payloadElem.appendChild(propertyElem); + } + compoundIter++; + } + } + } + return document; +} + +Arabica::DOM::Document Event::toDocument() { + Arabica::DOM::DOMImplementation domFactory = Arabica::SimpleDOM::DOMImplementation::getDOMImplementation(); + Arabica::DOM::Document document = Data::toDocument(); + Arabica::DOM::Element scxmlMsg = document.getDocumentElement(); + + scxmlMsg.setAttribute("source", origin); + scxmlMsg.setAttribute("name", name); + + return document; +} + +Arabica::DOM::Document SendRequest::toDocument() { + Arabica::DOM::DOMImplementation domFactory = Arabica::SimpleDOM::DOMImplementation::getDOMImplementation(); + Arabica::DOM::Document document = Event::toDocument(); + Arabica::DOM::Element scxmlMsg = document.getDocumentElement(); + + scxmlMsg.setAttribute("sendid", sendid); + + return document; +} + +Arabica::DOM::Document InvokeRequest::toDocument() { + Arabica::DOM::DOMImplementation domFactory = Arabica::SimpleDOM::DOMImplementation::getDOMImplementation(); + Arabica::DOM::Document document = Event::toDocument(); + Arabica::DOM::Element scxmlMsg = document.getDocumentElement(); + + scxmlMsg.setAttribute("invokeid", invokeid); + + return document; +} + +Data Data::fromXML(const std::string& xmlString) { +} + +Event Event::fromXML(const std::string& xmlString) { + Arabica::SAX2DOM::Parser eventParser; + Arabica::SAX::CatchErrorHandler errorHandler; + eventParser.setErrorHandler(errorHandler); + + std::istringstream is(xmlString); + Arabica::SAX::InputSource inputSource; + inputSource.setByteStream(is); + + Event event; + if(eventParser.parse(inputSource) && eventParser.getDocument().hasChildNodes()) { + Arabica::DOM::Element scxmlMsg = eventParser.getDocument().getDocumentElement(); + if (HAS_ATTR(scxmlMsg, "name")) + event.name = ATTR(scxmlMsg, "name"); + if (HAS_ATTR(scxmlMsg, "sendid")) + event.sendid = ATTR(scxmlMsg, "sendid"); + } + return event; +} + +SendRequest SendRequest::fromXML(const std::string& xmlString) { + Event::fromXML(xmlString); +} + +InvokeRequest InvokeRequest::fromXML(const std::string& xmlString) { + Event::fromXML(xmlString); +} + +#ifndef SWIGJAVA +std::ostream& operator<< (std::ostream& os, const Event& event) { + os << (event.type == Event::EXTERNAL ? "External" : "Internal") << " Event " << (event.dom ? "with DOM attached" : "") << std::endl; + + if (event.name.size() > 0) + os << " name: " << event.name << std::endl; + if (event.origin.size() > 0) + os << " origin: " << event.origin << std::endl; + if (event.origintype.size() > 0) + os << " origintype: " << event.origintype << std::endl; + _dataIndentation++; + os << " data: " << (Data)event << std::endl; + _dataIndentation--; + return os; +} +#endif + +#ifndef SWIGJAVA +std::ostream& operator<< (std::ostream& os, const Data& data) { + std::string indent; + for (int i = 0; i < _dataIndentation; i++) { + indent += " "; + } + if (false) { + } else if (data.compound.size() > 0) { + int longestKey = 0; + std::map::const_iterator compoundIter = data.compound.begin(); + while(compoundIter != data.compound.end()) { + if (compoundIter->first.size() > longestKey) + longestKey = compoundIter->first.size(); + compoundIter++; + } + std::string keyPadding; + for (unsigned int i = 0; i < longestKey; i++) + keyPadding += " "; + + os << "{" << std::endl; + compoundIter = data.compound.begin(); + while(compoundIter != data.compound.end()) { + os << indent << " \"" << compoundIter->first << "\" " << keyPadding.substr(0, longestKey - compoundIter->first.size()) << "= "; + _dataIndentation += 2; + os << compoundIter->second << "," << std::endl; + _dataIndentation -= 2; + compoundIter++; + } + os << indent << "}" << std::endl; + } else if (data.array.size() > 0) { + os << "[" << std::endl; + std::map::const_iterator compoundIter = data.compound.begin(); + while(compoundIter != data.compound.end()) { + _dataIndentation += 2; + os << indent << " " << compoundIter->second << "," << std::endl; + _dataIndentation -= 2; + compoundIter++; + } + os << indent << "]" << std::endl; + } else if (data.atom.size() > 0) { + if (data.type == Data::VERBATIM) { + os << indent << "\"" << data.atom << "\""; + } else { + os << indent << data.atom; + } + } + return os; +} +#endif + +} \ No newline at end of file diff --git a/src/uscxml/Message.h b/src/uscxml/Message.h new file mode 100644 index 0000000..a520ff5 --- /dev/null +++ b/src/uscxml/Message.h @@ -0,0 +1,122 @@ +#ifndef EVENT_H_XZAQ4HR +#define EVENT_H_XZAQ4HR + +#include +#include +#include + +#include +#include + +#define TAGNAME(elem) ((Arabica::DOM::Element)elem).getTagName() +#define ATTR(elem, attr) ((Arabica::DOM::Element)elem).getAttribute(attr) +#define HAS_ATTR(elem, attr) ((Arabica::DOM::Element)elem).hasAttribute(attr) + +namespace uscxml { + +class Data { +public: + enum Type { + VERBATIM, + INTERPRETED + }; + + Data() {} + Data(const std::string& atom_, Type type_ = INTERPRETED) : atom(atom_), type(type_) {} + virtual ~Data() {} + + static Data fromXML(const std::string& xmlString); + Arabica::DOM::Document toDocument(); + std::string toXMLString() { + std::stringstream ss; + ss << toDocument(); + return ss.str(); + } + + std::map compound; + std::vector array; + std::string atom; + Type type; + +protected: + Arabica::DOM::Document toNode(const Arabica::DOM::Document& factory, const Data& data); + +#ifndef SWIGJAVA + friend std::ostream& operator<< (std::ostream& os, const Data& data); +#endif +}; + +class Event : public Data { +public: + enum Type { + PLATFORM, + INTERNAL, + EXTERNAL + }; + + Event() : type(INTERNAL) {} + + std::string name; + Type type; + std::string origin; + std::string origintype; + Arabica::DOM::Node dom; + std::string sendid; + std::string invokeid; + + static Event fromXML(const std::string& xmlString); + Arabica::DOM::Document toDocument(); + std::string toXMLString() { + std::stringstream ss; + ss << toDocument(); + return ss.str(); + } + +#ifndef SWIGJAVA + friend std::ostream& operator<< (std::ostream& os, const Event& event); +#endif +}; + +class InvokeRequest : public Event { +public: + std::string type; + std::string src; + std::string namelist; + bool autoForward; + Arabica::DOM::Node finalize; + std::map params; + std::string content; + + static InvokeRequest fromXML(const std::string& xmlString); + Arabica::DOM::Document toDocument(); + std::string toXMLString() { + std::stringstream ss; + ss << toDocument(); + return ss.str(); + } + +}; + +class SendRequest : public Event { +public: + std::string target; + std::string type; + uint32_t delayMs; + std::map params; + std::map namelist; + std::string content; + + static SendRequest fromXML(const std::string& xmlString); + Arabica::DOM::Document toDocument(); + std::string toXMLString() { + std::stringstream ss; + ss << toDocument(); + return ss.str(); + } + +}; + +} + + +#endif /* end of include guard: EVENT_H_XZAQ4HR */ diff --git a/src/uscxml/concurrency/BlockingQueue.h b/src/uscxml/concurrency/BlockingQueue.h new file mode 100644 index 0000000..90094bf --- /dev/null +++ b/src/uscxml/concurrency/BlockingQueue.h @@ -0,0 +1,41 @@ +#ifndef BLOCKINGQUEUE_H_4LEVMY0N +#define BLOCKINGQUEUE_H_4LEVMY0N + +#include "uscxml/concurrency/tinythread.h" +#include + +namespace uscxml { +namespace concurrency { + +template +class BlockingQueue { +public: + BlockingQueue() {} + virtual ~BlockingQueue() { + } + + void push(T elem) { + tthread::lock_guard lock(_mutex); + _queue.push_back(elem); + _cond.notify_all(); + } + + T pop() { + tthread::lock_guard lock(_mutex); + while (_queue.empty()) { + _cond.wait(_mutex); + } + T ret = _queue.front(); + _queue.pop_front(); + return ret; + } + + tthread::mutex _mutex; + tthread::condition_variable _cond; + std::list _queue; +}; + +} +} + +#endif /* end of include guard: BLOCKINGQUEUE_H_4LEVMY0N */ diff --git a/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.cpp b/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.cpp new file mode 100644 index 0000000..a93b14a --- /dev/null +++ b/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.cpp @@ -0,0 +1,57 @@ +#include "uscxml/concurrency/DelayedEventQueue.h" +#include + +namespace uscxml { + + DelayedEventQueue::DelayedEventQueue() { + _eventLoop = EV_DEFAULT; + _thread = NULL; + } + + DelayedEventQueue::~DelayedEventQueue() { + ev_break(_eventLoop); + if (_thread) + _thread->join(); + } + + void DelayedEventQueue::addEvent(std::string eventId, void (*callback)(void*, const std::string eventId), uint32_t delayMs, void* userData) { + if(_timeoutWatcher.find(eventId) != _timeoutWatcher.end()) { + cancelEvent(eventId); + } + + _timeoutWatcher[eventId].eventId = eventId; + _timeoutWatcher[eventId].userData = userData; + _timeoutWatcher[eventId].eventQueue = this; + _timeoutWatcher[eventId].callback = callback; + + ev_timer_init (&_timeoutWatcher[eventId].io, DelayedEventQueue::timerCallback, ((float)delayMs)/1000.0f, 0.); + ev_timer_start (_eventLoop, &_timeoutWatcher[eventId].io); + + } + + void DelayedEventQueue::cancelEvent(std::string eventId) { + if(_timeoutWatcher.find(eventId) != _timeoutWatcher.end()) { + ev_timer_stop(_eventLoop, &_timeoutWatcher[eventId].io); + _timeoutWatcher.erase(eventId); + } + } + + void DelayedEventQueue::start() { + _thread = new tthread::thread(DelayedEventQueue::run, this); + } + + void DelayedEventQueue::stop() { + } + + void DelayedEventQueue::run(void* instance) { + ev_run (((DelayedEventQueue*)instance)->_eventLoop, 0); + } + + void DelayedEventQueue::timerCallback(EV_P_ ev_timer *w, int revents) { + struct callbackData *data = (struct callbackData*)w; + std::string eventId = data->eventId; // copy eventId + data->eventQueue->_timeoutWatcher.erase(data->eventId); + data->callback(data->userData, eventId); + } + +} \ No newline at end of file diff --git a/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.h b/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.h new file mode 100644 index 0000000..2bc71b2 --- /dev/null +++ b/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.h @@ -0,0 +1,45 @@ +#ifndef DELAYEDEVENTQUEUE_H_JA6WRBVP +#define DELAYEDEVENTQUEUE_H_JA6WRBVP + +#include "tinythread.h" +#include + +#include +#include +#include + +namespace uscxml { + +class DelayedEventQueue { +public: + + struct callbackData + { + ev_timer io; + void *userData; + void (*callback)(void*, const std::string eventId); + std::string eventId; + DelayedEventQueue* eventQueue; + }; + + DelayedEventQueue(); + virtual ~DelayedEventQueue(); + + void addEvent(std::string eventId, void (*callback)(void*, const std::string eventId), uint32_t delayMs, void* userData); + void cancelEvent(std::string eventId); + + void start(); + void stop(); + static void run(void*); + + static void timerCallback(EV_P_ ev_timer *w, int revents); + + tthread::thread* _thread; + std::map _timeoutWatcher; + struct ev_loop* _eventLoop; +}; + +} + + +#endif /* end of include guard: DELAYEDEVENTQUEUE_H_JA6WRBVP */ diff --git a/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.cpp b/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.cpp new file mode 100644 index 0000000..ce42af7 --- /dev/null +++ b/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.cpp @@ -0,0 +1,87 @@ +#include "uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h" +#include +#include + +namespace uscxml { + + DelayedEventQueue::DelayedEventQueue() { + evthread_use_pthreads(); + _eventLoop = event_base_new(); + _thread = NULL; + } + + DelayedEventQueue::~DelayedEventQueue() { + std::cout << "Deleting DelayedEventQueue" << std::endl; + if(_eventLoop) + event_base_loopbreak(_eventLoop); + if (_thread) + _thread->join(); + if(_eventLoop) + event_base_free(_eventLoop); + } + + void DelayedEventQueue::run(void* instance) { + DelayedEventQueue* THIS = (DelayedEventQueue*)instance; + int result; + while(THIS->_isStarted) { + { + //result = event_base_dispatch(THIS->_eventLoop); + result = event_base_loop(THIS->_eventLoop, EVLOOP_NO_EXIT_ON_EMPTY); + } + } + } + + void DelayedEventQueue::addEvent(std::string eventId, void (*callback)(void*, const std::string eventId), uint32_t delayMs, void* userData) { + if(_callbackData.find(eventId) != _callbackData.end()) { + cancelEvent(eventId); + } + + struct timeval delay = {delayMs / 1000, (delayMs % 1000) * 1000}; + struct event* event = event_new(_eventLoop, -1, 0, DelayedEventQueue::timerCallback, &_callbackData[eventId]); + + _callbackData[eventId].eventId = eventId; + _callbackData[eventId].userData = userData; + _callbackData[eventId].eventQueue = this; + _callbackData[eventId].callback = callback; + _callbackData[eventId].event = event; + + event_add(event, &delay); + } + + void DelayedEventQueue::cancelEvent(std::string eventId) { + tthread::lock_guard lock(_mutex); + + if(_callbackData.find(eventId) != _callbackData.end()) { + event_del(_callbackData[eventId].event); + event_free(_callbackData[eventId].event); + _callbackData.erase(eventId); + } + } + + void DelayedEventQueue::start() { + _isStarted = true; + _thread = new tthread::thread(DelayedEventQueue::run, this); + } + + void DelayedEventQueue::stop() { + if (_isStarted) { + _isStarted = false; + _thread->join(); + delete _thread; + } + } + + void DelayedEventQueue::dummyCallback(evutil_socket_t fd, short what, void *arg) { + } + + void DelayedEventQueue::timerCallback(evutil_socket_t fd, short what, void *arg) { + struct callbackData *data = (struct callbackData*)arg; + tthread::lock_guard lock(data->eventQueue->_mutex); + + std::string eventId = data->eventId; // copy eventId + event_free(data->event); + data->eventQueue->_callbackData.erase(data->eventId); + data->callback(data->userData, eventId); + } + +} \ No newline at end of file diff --git a/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h b/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h new file mode 100644 index 0000000..4c59ce1 --- /dev/null +++ b/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h @@ -0,0 +1,52 @@ +#ifndef DELAYEDEVENTQUEUE_H_JA6WRBVP +#define DELAYEDEVENTQUEUE_H_JA6WRBVP + +#include "uscxml/concurrency/tinythread.h" + +#include +#include +#include + +#include +#include +#include + +namespace uscxml { + +class DelayedEventQueue { +public: + + struct callbackData + { + void *userData; + void (*callback)(void*, const std::string eventId); + std::string eventId; + struct event *event; + DelayedEventQueue* eventQueue; + }; + + DelayedEventQueue(); + virtual ~DelayedEventQueue(); + + void addEvent(std::string eventId, void (*callback)(void*, const std::string eventId), uint32_t delayMs, void* userData); + void cancelEvent(std::string eventId); + + void start(); + void stop(); + static void run(void*); + + static void timerCallback(evutil_socket_t fd, short what, void *arg); + static void dummyCallback(evutil_socket_t fd, short what, void *arg); + + bool _isStarted; + tthread::thread* _thread; + tthread::recursive_mutex _mutex; + + std::map _callbackData; + struct event_base* _eventLoop; +}; + +} + + +#endif /* end of include guard: DELAYEDEVENTQUEUE_H_JA6WRBVP */ diff --git a/src/uscxml/concurrency/tinythread.cpp b/src/uscxml/concurrency/tinythread.cpp new file mode 100644 index 0000000..690ecee --- /dev/null +++ b/src/uscxml/concurrency/tinythread.cpp @@ -0,0 +1,303 @@ +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include "tinythread.h" + +#if defined(_TTHREAD_POSIX_) + #include + #include +#elif defined(_TTHREAD_WIN32_) + #include +#endif + + +namespace tthread { + +//------------------------------------------------------------------------------ +// condition_variable +//------------------------------------------------------------------------------ +// NOTE 1: The Win32 implementation of the condition_variable class is based on +// the corresponding implementation in GLFW, which in turn is based on a +// description by Douglas C. Schmidt and Irfan Pyarali: +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html +// +// NOTE 2: Windows Vista actually has native support for condition variables +// (InitializeConditionVariable, WakeConditionVariable, etc), but we want to +// be portable with pre-Vista Windows versions, so TinyThread++ does not use +// Vista condition variables. +//------------------------------------------------------------------------------ + +#if defined(_TTHREAD_WIN32_) + #define _CONDITION_EVENT_ONE 0 + #define _CONDITION_EVENT_ALL 1 +#endif + +#if defined(_TTHREAD_WIN32_) +condition_variable::condition_variable() : mWaitersCount(0) +{ + mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL); + mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL); + InitializeCriticalSection(&mWaitersCountLock); +} +#endif + +#if defined(_TTHREAD_WIN32_) +condition_variable::~condition_variable() +{ + CloseHandle(mEvents[_CONDITION_EVENT_ONE]); + CloseHandle(mEvents[_CONDITION_EVENT_ALL]); + DeleteCriticalSection(&mWaitersCountLock); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::_wait() +{ + // Wait for either event to become signaled due to notify_one() or + // notify_all() being called + int result = WaitForMultipleObjects(2, mEvents, FALSE, INFINITE); + + // Check if we are the last waiter + EnterCriticalSection(&mWaitersCountLock); + -- mWaitersCount; + bool lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) && + (mWaitersCount == 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we are the last waiter to be notified to stop waiting, reset the event + if(lastWaiter) + ResetEvent(mEvents[_CONDITION_EVENT_ALL]); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::notify_one() +{ + // Are there any waiters? + EnterCriticalSection(&mWaitersCountLock); + bool haveWaiters = (mWaitersCount > 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we have any waiting threads, send them a signal + if(haveWaiters) + SetEvent(mEvents[_CONDITION_EVENT_ONE]); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::notify_all() +{ + // Are there any waiters? + EnterCriticalSection(&mWaitersCountLock); + bool haveWaiters = (mWaitersCount > 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we have any waiting threads, send them a signal + if(haveWaiters) + SetEvent(mEvents[_CONDITION_EVENT_ALL]); +} +#endif + + +//------------------------------------------------------------------------------ +// POSIX pthread_t to unique thread::id mapping logic. +// Note: Here we use a global thread safe std::map to convert instances of +// pthread_t to small thread identifier numbers (unique within one process). +// This method should be portable across different POSIX implementations. +//------------------------------------------------------------------------------ + +#if defined(_TTHREAD_POSIX_) +static thread::id _pthread_t_to_ID(const pthread_t &aHandle) +{ + static mutex idMapLock; + static std::map idMap; + static unsigned long int idCount(1); + + lock_guard guard(idMapLock); + if(idMap.find(aHandle) == idMap.end()) + idMap[aHandle] = idCount ++; + return thread::id(idMap[aHandle]); +} +#endif // _TTHREAD_POSIX_ + + +//------------------------------------------------------------------------------ +// thread +//------------------------------------------------------------------------------ + +/// Information to pass to the new thread (what to run). +struct _thread_start_info { + void (*mFunction)(void *); ///< Pointer to the function to be executed. + void * mArg; ///< Function argument for the thread function. + thread * mThread; ///< Pointer to the thread object. +}; + +// Thread wrapper function. +#if defined(_TTHREAD_WIN32_) +unsigned WINAPI thread::wrapper_function(void * aArg) +#elif defined(_TTHREAD_POSIX_) +void * thread::wrapper_function(void * aArg) +#endif +{ + // Get thread startup information + _thread_start_info * ti = (_thread_start_info *) aArg; + + try + { + // Call the actual client thread function + ti->mFunction(ti->mArg); + } + catch(...) + { + // Uncaught exceptions will terminate the application (default behavior + // according to C++11) + std::terminate(); + } + + // The thread is no longer executing + lock_guard guard(ti->mThread->mDataMutex); + ti->mThread->mNotAThread = true; + + // The thread is responsible for freeing the startup information + delete ti; + + return 0; +} + +thread::thread(void (*aFunction)(void *), void * aArg) +{ + // Serialize access to this thread structure + lock_guard guard(mDataMutex); + + // Fill out the thread startup information (passed to the thread wrapper, + // which will eventually free it) + _thread_start_info * ti = new _thread_start_info; + ti->mFunction = aFunction; + ti->mArg = aArg; + ti->mThread = this; + + // The thread is now alive + mNotAThread = false; + + // Create the thread +#if defined(_TTHREAD_WIN32_) + mHandle = (HANDLE) _beginthreadex(0, 0, wrapper_function, (void *) ti, 0, &mWin32ThreadID); +#elif defined(_TTHREAD_POSIX_) + if(pthread_create(&mHandle, NULL, wrapper_function, (void *) ti) != 0) + mHandle = 0; +#endif + + // Did we fail to create the thread? + if(!mHandle) + { + mNotAThread = true; + delete ti; + } +} + +thread::~thread() +{ + if(joinable()) + std::terminate(); +} + +void thread::join() +{ + if(joinable()) + { +#if defined(_TTHREAD_WIN32_) + WaitForSingleObject(mHandle, INFINITE); + CloseHandle(mHandle); +#elif defined(_TTHREAD_POSIX_) + pthread_join(mHandle, NULL); +#endif + } +} + +bool thread::joinable() const +{ + mDataMutex.lock(); + bool result = !mNotAThread; + mDataMutex.unlock(); + return result; +} + +void thread::detach() +{ + mDataMutex.lock(); + if(!mNotAThread) + { +#if defined(_TTHREAD_WIN32_) + CloseHandle(mHandle); +#elif defined(_TTHREAD_POSIX_) + pthread_detach(mHandle); +#endif + mNotAThread = true; + } + mDataMutex.unlock(); +} + +thread::id thread::get_id() const +{ + if(!joinable()) + return id(); +#if defined(_TTHREAD_WIN32_) + return id((unsigned long int) mWin32ThreadID); +#elif defined(_TTHREAD_POSIX_) + return _pthread_t_to_ID(mHandle); +#endif +} + +unsigned thread::hardware_concurrency() +{ +#if defined(_TTHREAD_WIN32_) + SYSTEM_INFO si; + GetSystemInfo(&si); + return (int) si.dwNumberOfProcessors; +#elif defined(_SC_NPROCESSORS_ONLN) + return (int) sysconf(_SC_NPROCESSORS_ONLN); +#elif defined(_SC_NPROC_ONLN) + return (int) sysconf(_SC_NPROC_ONLN); +#else + // The standard requires this function to return zero if the number of + // hardware cores could not be determined. + return 0; +#endif +} + + +//------------------------------------------------------------------------------ +// this_thread +//------------------------------------------------------------------------------ + +thread::id this_thread::get_id() +{ +#if defined(_TTHREAD_WIN32_) + return thread::id((unsigned long int) GetCurrentThreadId()); +#elif defined(_TTHREAD_POSIX_) + return _pthread_t_to_ID(pthread_self()); +#endif +} + +} diff --git a/src/uscxml/concurrency/tinythread.h b/src/uscxml/concurrency/tinythread.h new file mode 100644 index 0000000..aed7b58 --- /dev/null +++ b/src/uscxml/concurrency/tinythread.h @@ -0,0 +1,714 @@ +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef _TINYTHREAD_H_ +#define _TINYTHREAD_H_ + +/// @file +/// @mainpage TinyThread++ API Reference +/// +/// @section intro_sec Introduction +/// TinyThread++ is a minimal, portable implementation of basic threading +/// classes for C++. +/// +/// They closely mimic the functionality and naming of the C++11 standard, and +/// should be easily replaceable with the corresponding std:: variants. +/// +/// @section port_sec Portability +/// The Win32 variant uses the native Win32 API for implementing the thread +/// classes, while for other systems, the POSIX threads API (pthread) is used. +/// +/// @section class_sec Classes +/// In order to mimic the threading API of the C++11 standard, subsets of +/// several classes are provided. The fundamental classes are: +/// @li tthread::thread +/// @li tthread::mutex +/// @li tthread::recursive_mutex +/// @li tthread::condition_variable +/// @li tthread::lock_guard +/// @li tthread::fast_mutex +/// +/// @section misc_sec Miscellaneous +/// The following special keywords are available: #thread_local. +/// +/// For more detailed information (including additional classes), browse the +/// different sections of this documentation. A good place to start is: +/// tinythread.h. + +// Which platform are we on? +#if !defined(_TTHREAD_PLATFORM_DEFINED_) + #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) + #define _TTHREAD_WIN32_ + #else + #define _TTHREAD_POSIX_ + #endif + #define _TTHREAD_PLATFORM_DEFINED_ +#endif + +// Platform specific includes +#if defined(_TTHREAD_WIN32_) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #define __UNDEF_LEAN_AND_MEAN + #endif + #include + #ifdef __UNDEF_LEAN_AND_MEAN + #undef WIN32_LEAN_AND_MEAN + #undef __UNDEF_LEAN_AND_MEAN + #endif +#else + #include + #include + #include + #include +#endif + +// Generic includes +#include + +/// TinyThread++ version (major number). +#define TINYTHREAD_VERSION_MAJOR 1 +/// TinyThread++ version (minor number). +#define TINYTHREAD_VERSION_MINOR 1 +/// TinyThread++ version (full version). +#define TINYTHREAD_VERSION (TINYTHREAD_VERSION_MAJOR * 100 + TINYTHREAD_VERSION_MINOR) + +// Do we have a fully featured C++11 compiler? +#if (__cplusplus > 199711L) || (defined(__STDCXX_VERSION__) && (__STDCXX_VERSION__ >= 201001L)) + #define _TTHREAD_CPP11_ +#endif + +// ...at least partial C++11? +#if defined(_TTHREAD_CPP11_) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__) + #define _TTHREAD_CPP11_PARTIAL_ +#endif + +// Macro for disabling assignments of objects. +#ifdef _TTHREAD_CPP11_PARTIAL_ + #define _TTHREAD_DISABLE_ASSIGNMENT(name) \ + name(const name&) = delete; \ + name& operator=(const name&) = delete; +#else + #define _TTHREAD_DISABLE_ASSIGNMENT(name) \ + name(const name&); \ + name& operator=(const name&); +#endif + +/// @def thread_local +/// Thread local storage keyword. +/// A variable that is declared with the @c thread_local keyword makes the +/// value of the variable local to each thread (known as thread-local storage, +/// or TLS). Example usage: +/// @code +/// // This variable is local to each thread. +/// thread_local int variable; +/// @endcode +/// @note The @c thread_local keyword is a macro that maps to the corresponding +/// compiler directive (e.g. @c __declspec(thread)). While the C++11 standard +/// allows for non-trivial types (e.g. classes with constructors and +/// destructors) to be declared with the @c thread_local keyword, most pre-C++11 +/// compilers only allow for trivial types (e.g. @c int). So, to guarantee +/// portable code, only use trivial types for thread local storage. +/// @note This directive is currently not supported on Mac OS X (it will give +/// a compiler error), since compile-time TLS is not supported in the Mac OS X +/// executable format. Also, some older versions of MinGW (before GCC 4.x) do +/// not support this directive. +/// @hideinitializer + +#if !defined(_TTHREAD_CPP11_) && !defined(thread_local) + #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) + #define thread_local __thread + #else + #define thread_local __declspec(thread) + #endif +#endif + + +/// Main name space for TinyThread++. +/// This namespace is more or less equivalent to the @c std namespace for the +/// C++11 thread classes. For instance, the tthread::mutex class corresponds to +/// the std::mutex class. +namespace tthread { + +/// Mutex class. +/// This is a mutual exclusion object for synchronizing access to shared +/// memory areas for several threads. The mutex is non-recursive (i.e. a +/// program may deadlock if the thread that owns a mutex object calls lock() +/// on that object). +/// @see recursive_mutex +class mutex { + public: + /// Constructor. + mutex() +#if defined(_TTHREAD_WIN32_) + : mAlreadyLocked(false) +#endif + { +#if defined(_TTHREAD_WIN32_) + InitializeCriticalSection(&mHandle); +#else + pthread_mutex_init(&mHandle, NULL); +#endif + } + + /// Destructor. + ~mutex() + { +#if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mHandle); +#else + pthread_mutex_destroy(&mHandle); +#endif + } + + /// Lock the mutex. + /// The method will block the calling thread until a lock on the mutex can + /// be obtained. The mutex remains locked until @c unlock() is called. + /// @see lock_guard + inline void lock() + { +#if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mHandle); + while(mAlreadyLocked) Sleep(1000); // Simulate deadlock... + mAlreadyLocked = true; +#else + pthread_mutex_lock(&mHandle); +#endif + } + + /// Try to lock the mutex. + /// The method will try to lock the mutex. If it fails, the function will + /// return immediately (non-blocking). + /// @return @c true if the lock was acquired, or @c false if the lock could + /// not be acquired. + inline bool try_lock() + { +#if defined(_TTHREAD_WIN32_) + bool ret = (TryEnterCriticalSection(&mHandle) ? true : false); + if(ret && mAlreadyLocked) + { + LeaveCriticalSection(&mHandle); + ret = false; + } + return ret; +#else + return (pthread_mutex_trylock(&mHandle) == 0) ? true : false; +#endif + } + + /// Unlock the mutex. + /// If any threads are waiting for the lock on this mutex, one of them will + /// be unblocked. + inline void unlock() + { +#if defined(_TTHREAD_WIN32_) + mAlreadyLocked = false; + LeaveCriticalSection(&mHandle); +#else + pthread_mutex_unlock(&mHandle); +#endif + } + + _TTHREAD_DISABLE_ASSIGNMENT(mutex) + + private: +#if defined(_TTHREAD_WIN32_) + CRITICAL_SECTION mHandle; + bool mAlreadyLocked; +#else + pthread_mutex_t mHandle; +#endif + + friend class condition_variable; +}; + +/// Recursive mutex class. +/// This is a mutual exclusion object for synchronizing access to shared +/// memory areas for several threads. The mutex is recursive (i.e. a thread +/// may lock the mutex several times, as long as it unlocks the mutex the same +/// number of times). +/// @see mutex +class recursive_mutex { + public: + /// Constructor. + recursive_mutex() + { +#if defined(_TTHREAD_WIN32_) + InitializeCriticalSection(&mHandle); +#else + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mHandle, &attr); +#endif + } + + /// Destructor. + ~recursive_mutex() + { +#if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mHandle); +#else + pthread_mutex_destroy(&mHandle); +#endif + } + + /// Lock the mutex. + /// The method will block the calling thread until a lock on the mutex can + /// be obtained. The mutex remains locked until @c unlock() is called. + /// @see lock_guard + inline void lock() + { +#if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mHandle); +#else + pthread_mutex_lock(&mHandle); +#endif + } + + /// Try to lock the mutex. + /// The method will try to lock the mutex. If it fails, the function will + /// return immediately (non-blocking). + /// @return @c true if the lock was acquired, or @c false if the lock could + /// not be acquired. + inline bool try_lock() + { +#if defined(_TTHREAD_WIN32_) + return TryEnterCriticalSection(&mHandle) ? true : false; +#else + return (pthread_mutex_trylock(&mHandle) == 0) ? true : false; +#endif + } + + /// Unlock the mutex. + /// If any threads are waiting for the lock on this mutex, one of them will + /// be unblocked. + inline void unlock() + { +#if defined(_TTHREAD_WIN32_) + LeaveCriticalSection(&mHandle); +#else + pthread_mutex_unlock(&mHandle); +#endif + } + + _TTHREAD_DISABLE_ASSIGNMENT(recursive_mutex) + + private: +#if defined(_TTHREAD_WIN32_) + CRITICAL_SECTION mHandle; +#else + pthread_mutex_t mHandle; +#endif + + friend class condition_variable; +}; + +/// Lock guard class. +/// The constructor locks the mutex, and the destructor unlocks the mutex, so +/// the mutex will automatically be unlocked when the lock guard goes out of +/// scope. Example usage: +/// @code +/// mutex m; +/// int counter; +/// +/// void increment() +/// { +/// lock_guard guard(m); +/// ++ counter; +/// } +/// @endcode + +template +class lock_guard { + public: + typedef T mutex_type; + + lock_guard() : mMutex(0) {} + + /// The constructor locks the mutex. + explicit lock_guard(mutex_type &aMutex) + { + mMutex = &aMutex; + mMutex->lock(); + } + + /// The destructor unlocks the mutex. + ~lock_guard() + { + if(mMutex) + mMutex->unlock(); + } + + private: + mutex_type * mMutex; +}; + +/// Condition variable class. +/// This is a signalling object for synchronizing the execution flow for +/// several threads. Example usage: +/// @code +/// // Shared data and associated mutex and condition variable objects +/// int count; +/// mutex m; +/// condition_variable cond; +/// +/// // Wait for the counter to reach a certain number +/// void wait_counter(int targetCount) +/// { +/// lock_guard guard(m); +/// while(count < targetCount) +/// cond.wait(m); +/// } +/// +/// // Increment the counter, and notify waiting threads +/// void increment() +/// { +/// lock_guard guard(m); +/// ++ count; +/// cond.notify_all(); +/// } +/// @endcode +class condition_variable { + public: + /// Constructor. +#if defined(_TTHREAD_WIN32_) + condition_variable(); +#else + condition_variable() + { + pthread_cond_init(&mHandle, NULL); + } +#endif + + /// Destructor. +#if defined(_TTHREAD_WIN32_) + ~condition_variable(); +#else + ~condition_variable() + { + pthread_cond_destroy(&mHandle); + } +#endif + + /// Wait for the condition. + /// The function will block the calling thread until the condition variable + /// is woken by @c notify_one(), @c notify_all() or a spurious wake up. + /// @param[in] aMutex A mutex that will be unlocked when the wait operation + /// starts, an locked again as soon as the wait operation is finished. + template + inline void wait(_mutexT &aMutex) + { +#if defined(_TTHREAD_WIN32_) + // Increment number of waiters + EnterCriticalSection(&mWaitersCountLock); + ++ mWaitersCount; + LeaveCriticalSection(&mWaitersCountLock); + + // Release the mutex while waiting for the condition (will decrease + // the number of waiters when done)... + aMutex.unlock(); + _wait(); + aMutex.lock(); +#else + pthread_cond_wait(&mHandle, &aMutex.mHandle); +#endif + } + + /// Notify one thread that is waiting for the condition. + /// If at least one thread is blocked waiting for this condition variable, + /// one will be woken up. + /// @note Only threads that started waiting prior to this call will be + /// woken up. +#if defined(_TTHREAD_WIN32_) + void notify_one(); +#else + inline void notify_one() + { + pthread_cond_signal(&mHandle); + } +#endif + + /// Notify all threads that are waiting for the condition. + /// All threads that are blocked waiting for this condition variable will + /// be woken up. + /// @note Only threads that started waiting prior to this call will be + /// woken up. +#if defined(_TTHREAD_WIN32_) + void notify_all(); +#else + inline void notify_all() + { + pthread_cond_broadcast(&mHandle); + } +#endif + + _TTHREAD_DISABLE_ASSIGNMENT(condition_variable) + + private: +#if defined(_TTHREAD_WIN32_) + void _wait(); + HANDLE mEvents[2]; ///< Signal and broadcast event HANDLEs. + unsigned int mWaitersCount; ///< Count of the number of waiters. + CRITICAL_SECTION mWaitersCountLock; ///< Serialize access to mWaitersCount. +#else + pthread_cond_t mHandle; +#endif +}; + + +/// Thread class. +class thread { + public: +#if defined(_TTHREAD_WIN32_) + typedef HANDLE native_handle_type; +#else + typedef pthread_t native_handle_type; +#endif + + class id; + + /// Default constructor. + /// Construct a @c thread object without an associated thread of execution + /// (i.e. non-joinable). + thread() : mHandle(0), mNotAThread(true) +#if defined(_TTHREAD_WIN32_) + , mWin32ThreadID(0) +#endif + {} + + /// Thread starting constructor. + /// Construct a @c thread object with a new thread of execution. + /// @param[in] aFunction A function pointer to a function of type: + /// void fun(void * arg) + /// @param[in] aArg Argument to the thread function. + /// @note This constructor is not fully compatible with the standard C++ + /// thread class. It is more similar to the pthread_create() (POSIX) and + /// CreateThread() (Windows) functions. + thread(void (*aFunction)(void *), void * aArg); + + /// Destructor. + /// @note If the thread is joinable upon destruction, @c std::terminate() + /// will be called, which terminates the process. It is always wise to do + /// @c join() before deleting a thread object. + ~thread(); + + /// Wait for the thread to finish (join execution flows). + /// After calling @c join(), the thread object is no longer associated with + /// a thread of execution (i.e. it is not joinable, and you may not join + /// with it nor detach from it). + void join(); + + /// Check if the thread is joinable. + /// A thread object is joinable if it has an associated thread of execution. + bool joinable() const; + + /// Detach from the thread. + /// After calling @c detach(), the thread object is no longer assicated with + /// a thread of execution (i.e. it is not joinable). The thread continues + /// execution without the calling thread blocking, and when the thread + /// ends execution, any owned resources are released. + void detach(); + + /// Return the thread ID of a thread object. + id get_id() const; + + /// Get the native handle for this thread. + /// @note Under Windows, this is a @c HANDLE, and under POSIX systems, this + /// is a @c pthread_t. + inline native_handle_type native_handle() + { + return mHandle; + } + + /// Determine the number of threads which can possibly execute concurrently. + /// This function is useful for determining the optimal number of threads to + /// use for a task. + /// @return The number of hardware thread contexts in the system. + /// @note If this value is not defined, the function returns zero (0). + static unsigned hardware_concurrency(); + + _TTHREAD_DISABLE_ASSIGNMENT(thread) + + private: + native_handle_type mHandle; ///< Thread handle. + mutable mutex mDataMutex; ///< Serializer for access to the thread private data. + bool mNotAThread; ///< True if this object is not a thread of execution. +#if defined(_TTHREAD_WIN32_) + unsigned int mWin32ThreadID; ///< Unique thread ID (filled out by _beginthreadex). +#endif + + // This is the internal thread wrapper function. +#if defined(_TTHREAD_WIN32_) + static unsigned WINAPI wrapper_function(void * aArg); +#else + static void * wrapper_function(void * aArg); +#endif +}; + +/// Thread ID. +/// The thread ID is a unique identifier for each thread. +/// @see thread::get_id() +class thread::id { + public: + /// Default constructor. + /// The default constructed ID is that of thread without a thread of + /// execution. + id() : mId(0) {}; + + id(unsigned long int aId) : mId(aId) {}; + + id(const id& aId) : mId(aId.mId) {}; + + inline id & operator=(const id &aId) + { + mId = aId.mId; + return *this; + } + + inline friend bool operator==(const id &aId1, const id &aId2) + { + return (aId1.mId == aId2.mId); + } + + inline friend bool operator!=(const id &aId1, const id &aId2) + { + return (aId1.mId != aId2.mId); + } + + inline friend bool operator<=(const id &aId1, const id &aId2) + { + return (aId1.mId <= aId2.mId); + } + + inline friend bool operator<(const id &aId1, const id &aId2) + { + return (aId1.mId < aId2.mId); + } + + inline friend bool operator>=(const id &aId1, const id &aId2) + { + return (aId1.mId >= aId2.mId); + } + + inline friend bool operator>(const id &aId1, const id &aId2) + { + return (aId1.mId > aId2.mId); + } + + inline friend std::ostream& operator <<(std::ostream &os, const id &obj) + { + os << obj.mId; + return os; + } + + private: + unsigned long int mId; +}; + + +// Related to - minimal to be able to support chrono. +typedef long long __intmax_t; + +/// Minimal implementation of the @c ratio class. This class provides enough +/// functionality to implement some basic @c chrono classes. +template <__intmax_t N, __intmax_t D = 1> class ratio { + public: + static double _as_double() { return double(N) / double(D); } +}; + +/// Minimal implementation of the @c chrono namespace. +/// The @c chrono namespace provides types for specifying time intervals. +namespace chrono { + /// Duration template class. This class provides enough functionality to + /// implement @c this_thread::sleep_for(). + template > class duration { + private: + _Rep rep_; + public: + typedef _Rep rep; + typedef _Period period; + + /// Construct a duration object with the given duration. + template + explicit duration(const _Rep2& r) : rep_(r) {}; + + /// Return the value of the duration object. + rep count() const + { + return rep_; + } + }; + + // Standard duration types. + typedef duration<__intmax_t, ratio<1, 1000000000> > nanoseconds; ///< Duration with the unit nanoseconds. + typedef duration<__intmax_t, ratio<1, 1000000> > microseconds; ///< Duration with the unit microseconds. + typedef duration<__intmax_t, ratio<1, 1000> > milliseconds; ///< Duration with the unit milliseconds. + typedef duration<__intmax_t> seconds; ///< Duration with the unit seconds. + typedef duration<__intmax_t, ratio<60> > minutes; ///< Duration with the unit minutes. + typedef duration<__intmax_t, ratio<3600> > hours; ///< Duration with the unit hours. +} + +/// The namespace @c this_thread provides methods for dealing with the +/// calling thread. +namespace this_thread { + /// Return the thread ID of the calling thread. + thread::id get_id(); + + /// Yield execution to another thread. + /// Offers the operating system the opportunity to schedule another thread + /// that is ready to run on the current processor. + inline void yield() + { +#if defined(_TTHREAD_WIN32_) + Sleep(0); +#else + sched_yield(); +#endif + } + + /// Blocks the calling thread for a period of time. + /// @param[in] aTime Minimum time to put the thread to sleep. + /// Example usage: + /// @code + /// // Sleep for 100 milliseconds + /// this_thread::sleep_for(chrono::milliseconds(100)); + /// @endcode + /// @note Supported duration types are: nanoseconds, microseconds, + /// milliseconds, seconds, minutes and hours. + template void sleep_for(const chrono::duration<_Rep, _Period>& aTime) + { +#if defined(_TTHREAD_WIN32_) + Sleep(int(double(aTime.count()) * (1000.0 * _Period::_as_double()) + 0.5)); +#else + usleep(int(double(aTime.count()) * (1000000.0 * _Period::_as_double()) + 0.5)); +#endif + } +} + +} + +// Define/macro cleanup +#undef _TTHREAD_DISABLE_ASSIGNMENT + +#endif // _TINYTHREAD_H_ diff --git a/src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp new file mode 100644 index 0000000..e31438a --- /dev/null +++ b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp @@ -0,0 +1,289 @@ +#include "uscxml/datamodel/ecmascript/v8/V8DataModel.h" +#include "uscxml/Message.h" + +namespace uscxml { + +V8DataModel::V8DataModel() { +// _contexts.push_back(v8::Context::New()); +} + +DataModel* V8DataModel::create(Interpreter* interpreter) { + V8DataModel* dm = new V8DataModel(); + dm->_interpreter = interpreter; + v8::Locker locker; + v8::HandleScope scope; + + // see http://stackoverflow.com/questions/3171418/v8-functiontemplate-class-instance + dm->_globalTemplate = v8::Persistent(v8::ObjectTemplate::New()); + dm->_globalTemplate->Set(v8::String::New("In"), v8::FunctionTemplate::New(jsIn, v8::External::New(reinterpret_cast(this)))); + + dm->_contexts.push_back(v8::Context::New(0, _globalTemplate)); + dm->setName(interpreter->getName()); + dm->setSessionId(interpreter->getSessionId()); + dm->eval("_ioprocessors = {};"); + return dm; +} + +void V8DataModel::setSessionId(const std::string& sessionId) { + _sessionId = sessionId; + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.front()); + v8::Handle global = _contexts.front()->Global(); + + global->Set(v8::String::New("_sessionid"), v8::String::New(sessionId.c_str())); +} + +void V8DataModel::setName(const std::string& name) { + _name = name; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.front()); + v8::Handle global = _contexts.front()->Global(); + + global->Set(v8::String::New("_name"), v8::String::New(name.c_str())); +} + +V8DataModel::~V8DataModel() { + while(_contexts.size() > 0) { + _contexts.back().Dispose(); + _contexts.pop_back(); + } +} + +void V8DataModel::pushContext() { + _contexts.push_back(_contexts.back().New(_contexts.back())); +} + +void V8DataModel::popContext() { + if (_contexts.size() > 1) { + _contexts.back().Dispose(); + _contexts.pop_back(); + } +} + +void V8DataModel::initialize() { +} + +void V8DataModel::setEvent(Event& event) { + _event = event; + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.front()); + v8::Handle global = _contexts.front()->Global(); + + // this is unfortunate - can't we store the template in the object? + if (_eventTemplate.IsEmpty()) { + v8::Handle localEventTemplate = v8::ObjectTemplate::New(); + localEventTemplate->SetInternalFieldCount(1); // we only have a single C++ object + localEventTemplate->SetAccessor(v8::String::New("name"), V8DataModel::jsGetEventName); + localEventTemplate->SetAccessor(v8::String::New("type"), V8DataModel::jsGetEventType); + localEventTemplate->SetAccessor(v8::String::New("sendid"), V8DataModel::jsGetEventSendId); + localEventTemplate->SetAccessor(v8::String::New("origin"), V8DataModel::jsGetEventOrigin); + localEventTemplate->SetAccessor(v8::String::New("origintype"), V8DataModel::jsGetEventOriginType); + localEventTemplate->SetAccessor(v8::String::New("invokeid"), V8DataModel::jsGetEventInvokeId); + _eventTemplate = v8::Persistent::New(localEventTemplate); + } + + assert(_eventTemplate->InternalFieldCount() == 1); + v8::Handle eventJS = _eventTemplate->NewInstance(); + eventJS->SetInternalField(0, v8::External::New(&event)); + + eventJS->Set(v8::String::New("data"), getDataAsValue(event)); // set data part of _event + global->Set(v8::String::New("_event"), eventJS); +} + +void V8DataModel::setData(const std::string& key, Data& data) { + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.front()); + v8::Handle global = _contexts.front()->Global(); + global->Set(v8::String::New(key.c_str()), getDataAsValue(data)); +} + +v8::Handle V8DataModel::getDataAsValue(Data& data) { + if (data.compound.size() > 0) { + v8::Handle value = v8::Array::New(); + std::map::iterator compoundIter = data.compound.begin(); + while(compoundIter != data.compound.end()) { + value->Set(v8::String::New(compoundIter->first.c_str()), getDataAsValue(compoundIter->second)); + compoundIter++; + } + return value; + } + if (data.array.size() > 0) { + v8::Handle value = v8::Array::New(); + std::vector::iterator arrayIter = data.array.begin(); + uint32_t index = 0; + while(arrayIter != data.array.end()) { + value->Set(index++, getDataAsValue(*arrayIter)); + arrayIter++; + } + return value; + } + if (data.type == Data::VERBATIM) { + return v8::String::New(data.atom.c_str()); + } else { + return evalAsValue(data.atom); + } +} + +v8::Handle V8DataModel::jsIn(const v8::Arguments& args) { + V8DataModel* THIS = static_cast(v8::External::Unwrap(args.Data())); + for (unsigned int i = 0; i < args.Length(); i++) { + if (args[i]->IsString()) { + std::string stateName(*v8::String::AsciiValue(args[i]->ToString())); + if (Interpreter::isMember(THIS->_interpreter->getState(stateName), THIS->_interpreter->getConfiguration())) { + continue; + } + } + return v8::Boolean::New(false); + } + return v8::Boolean::New(true); +} + +v8::Handle V8DataModel::jsGetEventName(v8::Local property, + const v8::AccessorInfo &info) { + Event* event = static_cast(v8::Local::Cast(info.Holder()->GetInternalField(0))->Value()); + return v8::String::New(event->name.c_str()); +} + +v8::Handle V8DataModel::jsGetEventType(v8::Local property, + const v8::AccessorInfo &info) { + Event* event = static_cast(v8::Local::Cast(info.Holder()->GetInternalField(0))->Value()); + switch (event->type) { + case Event::PLATFORM: + return v8::String::New("platform"); + break; + case Event::INTERNAL: + return v8::String::New("internal"); + break; + case Event::EXTERNAL: + return v8::String::New("external"); + break; + default: + return v8::String::New(""); + break; + } +} + +v8::Handle V8DataModel::jsGetEventSendId(v8::Local property, + const v8::AccessorInfo &info) { + Event* event = static_cast(v8::Local::Cast(info.Holder()->GetInternalField(0))->Value()); + return v8::String::New(event->sendid.c_str()); + +} + +v8::Handle V8DataModel::jsGetEventOrigin(v8::Local property, + const v8::AccessorInfo &info) { + Event* event = static_cast(v8::Local::Cast(info.Holder()->GetInternalField(0))->Value()); + return v8::String::New(event->origin.c_str()); +} + +v8::Handle V8DataModel::jsGetEventOriginType(v8::Local property, + const v8::AccessorInfo &info) { + Event* event = static_cast(v8::Local::Cast(info.Holder()->GetInternalField(0))->Value()); + return v8::String::New(event->origintype.c_str()); +} + +v8::Handle V8DataModel::jsGetEventInvokeId(v8::Local property, + const v8::AccessorInfo &info) { + Event* event = static_cast(v8::Local::Cast(info.Holder()->GetInternalField(0))->Value()); + return v8::String::New(event->invokeid.c_str()); +} + +bool V8DataModel::validate(const std::string& location, const std::string& schema) { + return true; +} + +uint32_t V8DataModel::getLength(const std::string& expr) { + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.back()); + v8::Handle result = evalAsValue(expr).As(); + return result->Length(); +} + +void V8DataModel::eval(const std::string& expr) { + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.back()); + evalAsValue(expr); +} + +bool V8DataModel::evalAsBool(const std::string& expr) { + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.back()); + v8::Handle result = evalAsValue(expr); + return(result->ToBoolean()->BooleanValue()); +} + +std::string V8DataModel::evalAsString(const std::string& expr) { + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.back()); + v8::Handle result = evalAsValue(expr); + v8::String::AsciiValue data(result->ToString()); + return std::string(*data); +} + +void V8DataModel::assign(const std::string& location, const std::string& expr) { + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.back()); + evalAsValue((location + " = " + expr).c_str()); +} + +v8::Handle V8DataModel::evalAsValue(const std::string& expr) { + v8::TryCatch tryCatch; + v8::Handle source = v8::String::New(expr.c_str()); + v8::Handle script = v8::Script::Compile(source); + + v8::Handle result; + if (!script.IsEmpty()) + result = script->Run(); + + if (script.IsEmpty() || result.IsEmpty()) { + // throw an exception + assert(tryCatch.HasCaught()); + Event exceptionEvent; + exceptionEvent.name = "error.execution"; + + std::string exceptionString(*v8::String::AsciiValue(tryCatch.Exception())); + exceptionEvent.compound["exception"] = Data(exceptionString, Data::VERBATIM);; + + v8::Handle message = tryCatch.Message(); + if (!message.IsEmpty()) { + std::string filename(*v8::String::AsciiValue(message->GetScriptResourceName())); + exceptionEvent.compound["filename"] = Data(filename, Data::VERBATIM); + + std::string sourceLine(*v8::String::AsciiValue(message->GetSourceLine())); + exceptionEvent.compound["sourceline"] = Data(sourceLine, Data::VERBATIM); + + std::stringstream ssLineNumber; + int lineNumber = message->GetLineNumber(); + ssLineNumber << lineNumber; + exceptionEvent.compound["linenumber"] = Data(ssLineNumber.str()); + + int startColumn = message->GetStartColumn(); + int endColumn = message->GetEndColumn(); + std::stringstream ssUnderline; + for (int i = 0; i < startColumn; i++) + ssUnderline << " "; + for (int i = startColumn; i < endColumn; i++) + ssUnderline << "^"; + exceptionEvent.compound["sourcemark"] = Data(ssUnderline.str(), Data::VERBATIM); + + std::string stackTrace(*v8::String::AsciiValue(tryCatch.StackTrace())); + exceptionEvent.compound["stacktrace"] = Data(stackTrace, Data::VERBATIM); + + } + + _interpreter->receiveInternal(exceptionEvent); + throw(exceptionEvent); + } + + return result; +} + +} \ No newline at end of file diff --git a/src/uscxml/datamodel/ecmascript/v8/V8DataModel.h b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.h new file mode 100644 index 0000000..7522679 --- /dev/null +++ b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.h @@ -0,0 +1,73 @@ +#ifndef V8DATAMODEL_H_KN8TWG0V +#define V8DATAMODEL_H_KN8TWG0V + +#include "uscxml/Interpreter.h" +#include +#include + +namespace uscxml { + class Event; + class Data; +} + +namespace uscxml { + +class V8DataModel : public DataModel { +public: + V8DataModel(); + virtual ~V8DataModel(); + virtual DataModel* create(Interpreter* interpreter); + + virtual void initialize(); + virtual void setSessionId(const std::string& sessionId); + virtual void setName(const std::string& name); + virtual void setEvent(Event& event); + virtual void setData(const std::string& key, Data& event); + virtual v8::Handle getDataAsValue(Data& data); + + virtual bool validate(const std::string& location, const std::string& schema); + + virtual uint32_t getLength(const std::string& expr); + virtual void pushContext(); + virtual void popContext(); + + virtual void eval(const std::string& expr); + virtual void assign(const std::string& location, const std::string& expr); + + virtual std::string evalAsString(const std::string& expr); + virtual bool evalAsBool(const std::string& expr); + + static v8::Handle jsGetEventName(v8::Local property, + const v8::AccessorInfo &info); + static v8::Handle jsGetEventType(v8::Local property, + const v8::AccessorInfo &info); + static v8::Handle jsGetEventSendId(v8::Local property, + const v8::AccessorInfo &info); + static v8::Handle jsGetEventOrigin(v8::Local property, + const v8::AccessorInfo &info); + static v8::Handle jsGetEventOriginType(v8::Local property, + const v8::AccessorInfo &info); + static v8::Handle jsGetEventInvokeId(v8::Local property, + const v8::AccessorInfo &info); + + static v8::Handle jsIn(const v8::Arguments& args); + + +protected: + std::list > _contexts; + Interpreter* _interpreter; + + std::string _sessionId; + std::string _name; + + Event _event; + v8::Persistent _globalTemplate; + v8::Persistent _eventTemplate; + + v8::Handle evalAsValue(const std::string& expr); + +}; + +} + +#endif /* end of include guard: V8DATAMODEL_H_KN8TWG0V */ diff --git a/src/uscxml/ioprocessor/basichttp/README.md b/src/uscxml/ioprocessor/basichttp/README.md new file mode 100644 index 0000000..de89944 --- /dev/null +++ b/src/uscxml/ioprocessor/basichttp/README.md @@ -0,0 +1,2 @@ +Only the libevent basichttp ioprocessor is supported. Mongoose seemed somewhat +unmaintained and pion comes with too many dependencies. \ No newline at end of file diff --git a/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp b/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp new file mode 100644 index 0000000..c06c7e8 --- /dev/null +++ b/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp @@ -0,0 +1,361 @@ +#include "uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h" +#include "uscxml/Message.h" +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +namespace uscxml { +namespace io { +namespace libevent { + +// see http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor + +EventIOProcessor::EventIOProcessor() { +} + +EventIOProcessor::~EventIOProcessor() { + _eventQueue.stop(); + EventIOServer* httpServer = EventIOServer::getInstance(); + httpServer->unregisterProcessor(this); +} + +IOProcessor* EventIOProcessor::create(Interpreter* interpreter) { + EventIOProcessor* io = new EventIOProcessor(); + io->_interpreter = interpreter; + + io->_dns = evdns_base_new(io->_eventQueue._eventLoop, 1); + assert(io->_dns); + assert(evdns_base_count_nameservers(io->_dns) > 0); + + // register at http server + EventIOServer* httpServer = EventIOServer::getInstance(); + httpServer->registerProcessor(io); + + io->start(); + return io; +} + +void EventIOProcessor::start() { + _eventQueue.start(); +} + +void EventIOProcessor::send(SendRequest& req) { + // I cant figure out how to copy the reference into the struct :( + _sendData[req.sendid].req = req; + _sendData[req.sendid].ioProcessor = this; + + if (req.delayMs > 0) { + LOG(INFO) << "Enqueing HTTP send request"; + _eventQueue.addEvent(req.sendid, EventIOProcessor::httpMakeSendReq, req.delayMs, &_sendData[req.sendid]); + } else { + LOG(INFO) << "Sending HTTP send request"; + EventIOProcessor::httpMakeSendReq(&_sendData[req.sendid], req.sendid); + } +} + +void EventIOProcessor::httpMakeSendReq(void* userdata, std::string eventName) { + SendData* sendData = ((SendData*)userdata); + EventIOProcessor* THIS = sendData->ioProcessor; + int err = 0; + char uriBuf[1024]; + + struct evhttp_uri* targetURI = evhttp_uri_parse(sendData->req.target.c_str()); + if (evhttp_uri_get_port(targetURI) == 0) + evhttp_uri_set_port(targetURI, 80); + const char* hostName = evhttp_uri_get_host(targetURI); + + // use synchronous dns resolving for multicast dns + if(strlen(hostName) >= strlen(".local")) { + if(strcmp(hostName + strlen(hostName) - strlen(".local"), ".local") == 0) { + evhttp_uri_set_host(targetURI, EventIOServer::syncResolve(hostName).c_str()); + } + } + evhttp_uri_join(targetURI, uriBuf, 1024); + + LOG(INFO) << "URI for send request: " << uriBuf << std::endl; + + std::stringstream ssEndPoint; + ssEndPoint << evhttp_uri_get_host(targetURI) << ":" << evhttp_uri_get_port(targetURI); + std::string endPoint = ssEndPoint.str(); + + std::stringstream ssLocalURI; + ssLocalURI << evhttp_uri_get_path(targetURI) << evhttp_uri_get_fragment(targetURI); + std::string localURI = ssLocalURI.str(); + + if (THIS->_httpConnections.find(endPoint) == THIS->_httpConnections.end()) + THIS->_httpConnections[endPoint] = evhttp_connection_base_new(THIS->_eventQueue._eventLoop, THIS->_dns, evhttp_uri_get_host(targetURI), evhttp_uri_get_port(targetURI)); + + struct evhttp_connection* httpConn = THIS->_httpConnections[endPoint]; + struct evhttp_request* httpReq = evhttp_request_new(EventIOProcessor::httpSendReqDone, userdata); + +#if 0 + // event name + if (sendData->req.event.size() > 0) { + evhttp_add_header(evhttp_request_get_output_headers(httpReq), "_scxmleventname", evhttp_encode_uri(sendData->req.event.c_str())); + } + + // event namelist + if (sendData->req.namelist.size() > 0) { + std::map::iterator namelistIter = sendData->req.namelist.begin(); + while (namelistIter != sendData->req.namelist.end()) { + evhttp_add_header(evhttp_request_get_output_headers(httpReq), + namelistIter->first.c_str(), + evhttp_encode_uri(namelistIter->second.c_str())); + namelistIter++; + } + } + + // event params + if (sendData->req.params.size() > 0) { + std::map::iterator paramIter = sendData->req.params.begin(); + while (paramIter != sendData->req.params.end()) { + evhttp_add_header(evhttp_request_get_output_headers(httpReq), + paramIter->first.c_str(), + evhttp_encode_uri(paramIter->second.c_str())); + paramIter++; + } + } + + // content + if (sendData->req.content.size() > 0) + evbuffer_add(evhttp_request_get_output_buffer(httpReq), sendData->req.content.c_str(), sendData->req.content.size()); +#endif + + evhttp_add_header(evhttp_request_get_output_headers(httpReq), "_scxmleventstruct", evhttp_encode_uri(sendData->req.toXMLString().c_str())); + + + THIS->_httpRequests[sendData->req.sendid] = httpReq; + err = evhttp_make_request(httpConn, + httpReq, + EVHTTP_REQ_POST, localURI.c_str()); + if (err) { + LOG(ERROR) << "Could not make http request to " << sendData->req.target; + } +} + +void EventIOProcessor::httpRecvReq(struct evhttp_request *req, void *arg) { + + const char *cmdtype; + struct evkeyvalq *headers; + struct evkeyval *header; + struct evbuffer *buf; + + switch (evhttp_request_get_command(req)) { + case EVHTTP_REQ_GET: cmdtype = "GET"; break; + case EVHTTP_REQ_POST: cmdtype = "POST"; break; + case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break; + case EVHTTP_REQ_PUT: cmdtype = "PUT"; break; + case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break; + case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break; + case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break; + case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break; + case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break; + default: cmdtype = "unknown"; break; + } + + Event reqEvent; + reqEvent.type = Event::EXTERNAL; + + // map headers to event structure + headers = evhttp_request_get_input_headers(req); + for (header = headers->tqh_first; header; + header = header->next.tqe_next) { +// std::cout << "Header: " << header->key << std::endl; +// std::cout << "Value: " << evhttp_decode_uri(header->value) << std::endl; + if (boost::iequals("_scxmleventstruct", header->key)) { + reqEvent = Event::fromXML(evhttp_decode_uri(header->value)); + break; + } else if (boost::iequals("_scxmleventname", header->key)) { + reqEvent.name = evhttp_decode_uri(header->value); + } else { + reqEvent.compound[header->key] = Data(evhttp_decode_uri(header->value), Data::VERBATIM); + } + } + + // get content into event + std::string content; + buf = evhttp_request_get_input_buffer(req); + while (evbuffer_get_length(buf)) { + int n; + char cbuf[128]; + n = evbuffer_remove(buf, cbuf, sizeof(buf)-1); + if (n > 0) { + content.append(cbuf, n); + } + } + reqEvent.compound["content"] = Data(content, Data::VERBATIM); + + EventIOProcessor* THIS = (EventIOProcessor*)arg; + THIS->_interpreter->receive(reqEvent); + + evhttp_send_reply(req, 200, "OK", NULL); +} + +void EventIOProcessor::httpSendReqDone(struct evhttp_request *req, void *cb_arg) { + if (req) { + LOG(INFO) << "got return code " << evhttp_request_get_response_code(req) << std::endl; + } +} + +void EventIOProcessor::invoke(InvokeRequest& req) { + +} +void EventIOProcessor::cancel(const std::string sendId) { + +} + +EventIOServer::EventIOServer(unsigned short port) { + _port = port; + _base = event_base_new(); + _http = evhttp_new(_base); + _handle = NULL; + while((_handle = evhttp_bind_socket_with_handle(_http, INADDR_ANY, _port)) == NULL) { + _port++; + } + determineAddress(); +} + +EventIOServer::~EventIOServer() { +} + +EventIOServer* EventIOServer::_instance = NULL; +tthread::recursive_mutex EventIOServer::_instanceMutex; + +EventIOServer* EventIOServer::getInstance() { + tthread::lock_guard lock(_instanceMutex); + if (_instance == NULL) { + _instance = new EventIOServer(8080); + _instance->start(); + } + return _instance; +} + +void EventIOServer::registerProcessor(EventIOProcessor* processor) { + EventIOServer* THIS = getInstance(); + tthread::lock_guard lock(THIS->_mutex); + + /** + * Determine path for interpreter. + * + * If the interpreter has a name and it is not yet taken, choose it as the path + * for requests. If the interpreters name path is already taken, append digits + * until we have an available path. + * + * If the interpreter does not specify a name, take its sessionid. + */ + + std::string path = processor->_interpreter->getName(); + if (path.size() == 0) { + path = processor->_interpreter->getSessionId(); + } + assert(path.size() > 0); + + std::stringstream actualPath(path); + int i = 1; + while(THIS->_processors.find(actualPath.str()) != THIS->_processors.end()) { + actualPath.str(std::string()); + actualPath.clear(); + actualPath << path << ++i; + } + + std::stringstream processorURL; + processorURL << "http://" << THIS->_address << ":" << THIS->_port << "/" << actualPath.str(); + + THIS->_processors[actualPath.str()] = processor; + processor->setURL(processorURL.str()); + + evhttp_set_cb(THIS->_http, ("/" + actualPath.str()).c_str(), EventIOProcessor::httpRecvReq, processor); +// evhttp_set_cb(THIS->_http, "/", EventIOProcessor::httpRecvReq, processor); +// evhttp_set_gencb(THIS->_http, EventIOProcessor::httpRecvReq, NULL); +} + +void EventIOServer::unregisterProcessor(EventIOProcessor* processor) { + EventIOServer* THIS = getInstance(); + tthread::lock_guard lock(THIS->_mutex); + evhttp_del_cb(THIS->_http, processor->getURL().c_str()); +} + +void EventIOServer::start() { + _isRunning = true; + _thread = new tthread::thread(EventIOServer::run, this); +} + +void EventIOServer::run(void* instance) { + EventIOServer* THIS = (EventIOServer*)instance; + while(THIS->_isRunning) { + LOG(INFO) << "Dispatching HTTP Server" << std::endl; + event_base_dispatch(THIS->_base); + } + LOG(INFO) << "HTTP Server stopped" << std::endl; +} + +std::string EventIOServer::syncResolve(const std::string& hostname) { + struct hostent *he; + struct in_addr **addr_list; + int i; + + if ( (he = gethostbyname( hostname.c_str() ) ) != NULL) { + addr_list = (struct in_addr **) he->h_addr_list; + for(i = 0; addr_list[i] != NULL; i++) { + return std::string(inet_ntoa(*addr_list[i])); + } + } + return ""; +} + +void EventIOServer::determineAddress() { + + char hostname[1024]; + gethostname(hostname, 1024); + _address = std::string(hostname); + +#if 0 + struct sockaddr_storage ss; + evutil_socket_t fd; + ev_socklen_t socklen = sizeof(ss); + char addrbuf[128]; + + void *inaddr; + const char *addr; + int got_port = -1; + fd = evhttp_bound_socket_get_fd(_handle); + memset(&ss, 0, sizeof(ss)); + if (getsockname(fd, (struct sockaddr *)&ss, &socklen)) { + perror("getsockname() failed"); + return; + } + + if (ss.ss_family == AF_INET) { + got_port = ntohs(((struct sockaddr_in*)&ss)->sin_port); + inaddr = &((struct sockaddr_in*)&ss)->sin_addr; + } else if (ss.ss_family == AF_INET6) { + got_port = ntohs(((struct sockaddr_in6*)&ss)->sin6_port); + inaddr = &((struct sockaddr_in6*)&ss)->sin6_addr; + } else { + fprintf(stderr, "Weird address family %d\n", + ss.ss_family); + return; + } + addr = evutil_inet_ntop(ss.ss_family, inaddr, addrbuf, + sizeof(addrbuf)); + if (addr) { + _address = std::string(addr); + } else { + fprintf(stderr, "evutil_inet_ntop failed\n"); + return; + } +#endif +} + +} +} +} \ No newline at end of file diff --git a/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h b/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h new file mode 100644 index 0000000..7e8eaa9 --- /dev/null +++ b/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h @@ -0,0 +1,96 @@ +#ifndef EVENTIOPROCESSOR_H_2CUY93KU +#define EVENTIOPROCESSOR_H_2CUY93KU + +#include "uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h" +#include "uscxml/Interpreter.h" +#include "uscxml/Factory.h" +#include + +#include +#include + +namespace uscxml { +namespace io { +namespace libevent { + +class EventIOServer; + +class EventIOProcessor : public uscxml::IOProcessor { +public: + struct SendData { + EventIOProcessor* ioProcessor; + uscxml::SendRequest req; + }; + + EventIOProcessor(); + virtual ~EventIOProcessor(); + virtual IOProcessor* create(uscxml::Interpreter* interpreter); + + virtual void send(uscxml::SendRequest& req); + virtual void invoke(uscxml::InvokeRequest& req); + virtual void cancel(const std::string sendId); + + std::string getURL() { return _url; } + void setURL(const std::string& url) { _url = url; } + + void start(); + static void run(void* instance); + + static void httpMakeSendReq(void* userdata, std::string eventName); + static void httpSendReqDone(struct evhttp_request *req, void *cb_arg); + static void httpRecvReq(struct evhttp_request *req, void *arg); + +protected: + std::map _sendData; + + std::string _url; + + uscxml::DelayedEventQueue _eventQueue; + uscxml::Interpreter* _interpreter; + std::map _httpConnections; + std::map _httpRequests; + struct evdns_base* _dns; + + friend class EventIOServer; +}; + +class EventIOServer { +private: + static EventIOServer* getInstance(); + EventIOServer(unsigned short port); + ~EventIOServer(); + + void start(); + void stop(); + static void run(void* instance); + + void determineAddress(); + static std::string syncResolve(const std::string& hostname); + + static void registerProcessor(EventIOProcessor* processor); + static void unregisterProcessor(EventIOProcessor* processor); + + + std::map _processors; + + struct event_base* _base; + struct evhttp* _http; + struct evhttp_bound_socket* _handle; + + unsigned short _port; + std::string _address; + + static EventIOServer* _instance; + static tthread::recursive_mutex _instanceMutex; + tthread::thread* _thread; + tthread::recursive_mutex _mutex; + bool _isRunning; + + friend class EventIOProcessor; +}; + +} +} +} + +#endif /* end of include guard: EVENTIOPROCESSOR_H_2CUY93KU */ \ No newline at end of file diff --git a/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.cpp b/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.cpp new file mode 100644 index 0000000..a62fefc --- /dev/null +++ b/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.cpp @@ -0,0 +1,3 @@ +#include "uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.h" +#include "uscxml/Message.h" + diff --git a/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.h b/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.h new file mode 100644 index 0000000..bb7a0fc --- /dev/null +++ b/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.h @@ -0,0 +1,15 @@ +#ifndef MONGOOSEIOPROCESSOR_H_JS0GMSFO +#define MONGOOSEIOPROCESSOR_H_JS0GMSFO + +#include "uscxml/Interpreter.h" +#include "uscxml/Factory.h" + +namespace uscxml { + +class MongooseIOProcessor : public IOProcessor { + +}; + +} + +#endif /* end of include guard: MONGOOSEIOPROCESSOR_H_JS0GMSFO */ diff --git a/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.cpp b/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.cpp new file mode 100644 index 0000000..7aa9169 --- /dev/null +++ b/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.cpp @@ -0,0 +1,74 @@ +#include "uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h" +#include "uscxml/Message.h" + +#include +#include + +namespace uscxml { + +using namespace pion; + +PionIOProcessor::PionIOProcessor() { +} + +PionIOProcessor::~PionIOProcessor() { + +} + +IOProcessor* PionIOProcessor::create(Interpreter* interpreter) { + PionIOProcessor* io = new PionIOProcessor(); + io->_interpreter = interpreter; + io->_ioServer = PionIOServer::getInstance(); + return io; +} + +void handle_connection(pion::tcp::connection_ptr& tcp_conn) { +} + +void PionIOProcessor::send(SendRequest& req) { + + boost::system::error_code error_code; + boost::asio::io_service io_service; + + pion::tcp::connection tcp_conn(io_service, 0); + error_code = tcp_conn.connect("localhost", 8080); + if (error_code) throw error_code; // connection failed + + + + http::request httpReq; + httpReq.set_method("POST"); + if (req.event.size() > 0) + httpReq.add_header("_scxmleventname", req.event); + + httpReq.send(tcp_conn, error_code); + +// http::request_writer writer; +// writer. + +} +void PionIOProcessor::invoke(InvokeRequest& req) { + +} +void PionIOProcessor::cancel(const std::string sendId) { + +} + +PionIOServer::PionIOServer() : pion::tcp::server(0) { +} + +PionIOServer::~PionIOServer() { +} + +void PionIOServer::handle_connection(pion::tcp::connection_ptr& tcp_conn) { +} + +PionIOServer* PionIOServer::_instance = NULL; +PionIOServer* PionIOServer::getInstance() { + if (_instance == NULL) { + _instance = new PionIOServer(); + } + return _instance; +} + +} \ No newline at end of file diff --git a/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h b/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h new file mode 100644 index 0000000..154acdb --- /dev/null +++ b/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h @@ -0,0 +1,47 @@ +#ifndef PIONIOPROCESSOR_H_VAITDNCN +#define PIONIOPROCESSOR_H_VAITDNCN + +#include "uscxml/Interpreter.h" +#include "uscxml/Factory.h" +#include "uscxml/concurrency/DelayedEventQueue.h" + +#include +#include +#include + +namespace uscxml { + +class PionIOServer : public pion::tcp::server { +public: + PionIOServer(); + virtual ~PionIOServer(); + DelayedEventQueue _eventQueue; + + virtual void handle_connection(pion::tcp::connection_ptr& tcp_conn); + + static PionIOServer* getInstance(); + static PionIOServer* _instance; + + pion::http::request_writer_ptr _writer; + pion::tcp::connection_ptr _conn; + +}; + +class PionIOProcessor : public IOProcessor { +public: + PionIOProcessor(); + virtual ~PionIOProcessor(); + virtual IOProcessor* create(Interpreter* interpreter); + + virtual void send(SendRequest& req); + virtual void invoke(InvokeRequest& req); + virtual void cancel(const std::string sendId); + +protected: + Interpreter* _interpreter; + PionIOServer* _ioServer; +}; + +} + +#endif /* end of include guard: PIONIOPROCESSOR_H_VAITDNCN */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..297428c --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,29 @@ +add_executable(test-predicates src/test-predicates.cpp) +target_link_libraries(test-predicates uscxml) +add_test(test-predicates ${CMAKE_SOURCE_DIR}/test/src/test-predicates.scxml) +set_target_properties(test-predicates PROPERTIES FOLDER "Tests") + +add_executable(test-execution src/test-execution.cpp) +target_link_libraries(test-execution uscxml) +add_test(test-execution ${CMAKE_SOURCE_DIR}/test/src/test-execution.scxml) +set_target_properties(test-execution PROPERTIES FOLDER "Tests") + +add_executable(test-apache-commons src/test-apache-commons.cpp) +target_link_libraries(test-apache-commons uscxml) +add_test(test-apache-commons ${CMAKE_SOURCE_DIR}/test/apache) +set_target_properties(test-apache-commons PROPERTIES FOLDER "Tests") + +add_executable(test-ecmascript-v8 src/test-ecmascript-v8.cpp) +target_link_libraries(test-ecmascript-v8 uscxml) +add_test(test-ecmascript-v8 test-ecmascript-v8) +set_target_properties(test-ecmascript-v8 PROPERTIES FOLDER "Tests") + +add_executable(test-communication src/test-communication.cpp) +target_link_libraries(test-communication uscxml) +add_test(test-communication test-communication) +set_target_properties(test-communication PROPERTIES FOLDER "Tests") + +add_executable(test-eventdelay src/test-eventdelay.cpp) +target_link_libraries(test-eventdelay uscxml) +add_test(test-eventdelay test-eventdelay) +set_target_properties(test-eventdelay PROPERTIES FOLDER "Tests") diff --git a/test/samples/apache/actions-initial-test.xml b/test/samples/apache/actions-initial-test.xml new file mode 100644 index 0000000..d2d3e48 --- /dev/null +++ b/test/samples/apache/actions-initial-test.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/samples/apache/actions-parallel-test.xml b/test/samples/apache/actions-parallel-test.xml new file mode 100644 index 0000000..be9d25c --- /dev/null +++ b/test/samples/apache/actions-parallel-test.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/samples/apache/actions-state-test.xml b/test/samples/apache/actions-state-test.xml new file mode 100644 index 0000000..3374e21 --- /dev/null +++ b/test/samples/apache/actions-state-test.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/samples/apache/assign-test-01.xml b/test/samples/apache/assign-test-01.xml new file mode 100644 index 0000000..ed7e977 --- /dev/null +++ b/test/samples/apache/assign-test-01.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + 10 + + + 20 + + + + + + + + + + + + + + diff --git a/test/samples/apache/assign-test-02.xml b/test/samples/apache/assign-test-02.xml new file mode 100644 index 0000000..38ab9b3 --- /dev/null +++ b/test/samples/apache/assign-test-02.xml @@ -0,0 +1,46 @@ + + + + + + + + 1 + 2 + + + + + 3 + 4 + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/bar.xml b/test/samples/apache/bar.xml new file mode 100644 index 0000000..3901080 --- /dev/null +++ b/test/samples/apache/bar.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/test/samples/apache/custom-hello-world-01.xml b/test/samples/apache/custom-hello-world-01.xml new file mode 100644 index 0000000..5d8a348 --- /dev/null +++ b/test/samples/apache/custom-hello-world-01.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/custom-hello-world-02.xml b/test/samples/apache/custom-hello-world-02.xml new file mode 100644 index 0000000..7b27ccf --- /dev/null +++ b/test/samples/apache/custom-hello-world-02.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + diff --git a/test/samples/apache/custom-hello-world-03.xml b/test/samples/apache/custom-hello-world-03.xml new file mode 100644 index 0000000..c8cfc5e --- /dev/null +++ b/test/samples/apache/custom-hello-world-03.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + diff --git a/test/samples/apache/custom-hello-world-04-el.xml b/test/samples/apache/custom-hello-world-04-el.xml new file mode 100644 index 0000000..0d5300b --- /dev/null +++ b/test/samples/apache/custom-hello-world-04-el.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/custom-hello-world-04-jexl.xml b/test/samples/apache/custom-hello-world-04-jexl.xml new file mode 100644 index 0000000..c44e9b3 --- /dev/null +++ b/test/samples/apache/custom-hello-world-04-jexl.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/datamodel-01.xml b/test/samples/apache/datamodel-01.xml new file mode 100644 index 0000000..cc874fb --- /dev/null +++ b/test/samples/apache/datamodel-01.xml @@ -0,0 +1,107 @@ + + + + + + + + + + foo + + + + + + + + + + + + + + + + + bar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/datamodel-02.xml b/test/samples/apache/datamodel-02.xml new file mode 100644 index 0000000..b719b8b --- /dev/null +++ b/test/samples/apache/datamodel-02.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + 10 + + + + + + + + + + + + + + + + 20 + + + + + + + + + + + + + + + + 30 + + + + + + + + + + + + + diff --git a/test/samples/apache/datamodel-03.xml b/test/samples/apache/datamodel-03.xml new file mode 100644 index 0000000..f8a3310 --- /dev/null +++ b/test/samples/apache/datamodel-03.xml @@ -0,0 +1,259 @@ + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + 10 + + + + + + + + + + + + + + + + + + + + + + + + + + 20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 41 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/datamodel-04.xml b/test/samples/apache/datamodel-04.xml new file mode 100644 index 0000000..503a858 --- /dev/null +++ b/test/samples/apache/datamodel-04.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/edit-profile-config.xml b/test/samples/apache/edit-profile-config.xml new file mode 100644 index 0000000..cdab498 --- /dev/null +++ b/test/samples/apache/edit-profile-config.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/eventdata-01.xml b/test/samples/apache/eventdata-01.xml new file mode 100644 index 0000000..c07d42d --- /dev/null +++ b/test/samples/apache/eventdata-01.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/eventdata-02.xml b/test/samples/apache/eventdata-02.xml new file mode 100644 index 0000000..df130ae --- /dev/null +++ b/test/samples/apache/eventdata-02.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/eventdata-03.xml b/test/samples/apache/eventdata-03.xml new file mode 100644 index 0000000..d4c4388 --- /dev/null +++ b/test/samples/apache/eventdata-03.xml @@ -0,0 +1,49 @@ + + + + + + + + 1 + 2 + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/eventdata-04.xml b/test/samples/apache/eventdata-04.xml new file mode 100644 index 0000000..541d2e9 --- /dev/null +++ b/test/samples/apache/eventdata-04.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/external-hello-world.xml b/test/samples/apache/external-hello-world.xml new file mode 100644 index 0000000..b6d1b9f --- /dev/null +++ b/test/samples/apache/external-hello-world.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/test/samples/apache/foo.xml b/test/samples/apache/foo.xml new file mode 100644 index 0000000..486d36e --- /dev/null +++ b/test/samples/apache/foo.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/test/samples/apache/hello-world.xml b/test/samples/apache/hello-world.xml new file mode 100644 index 0000000..7647899 --- /dev/null +++ b/test/samples/apache/hello-world.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + diff --git a/test/samples/apache/history-deep-01.xml b/test/samples/apache/history-deep-01.xml new file mode 100644 index 0000000..209a974 --- /dev/null +++ b/test/samples/apache/history-deep-01.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/history-default-01.xml b/test/samples/apache/history-default-01.xml new file mode 100644 index 0000000..cf2f70f --- /dev/null +++ b/test/samples/apache/history-default-01.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/history-shallow-01.xml b/test/samples/apache/history-shallow-01.xml new file mode 100644 index 0000000..20b5bb5 --- /dev/null +++ b/test/samples/apache/history-shallow-01.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/invoked-01.xml b/test/samples/apache/invoked-01.xml new file mode 100644 index 0000000..a523995 --- /dev/null +++ b/test/samples/apache/invoked-01.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + diff --git a/test/samples/apache/invoked-02.xml b/test/samples/apache/invoked-02.xml new file mode 100644 index 0000000..b1b471a --- /dev/null +++ b/test/samples/apache/invoked-02.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + diff --git a/test/samples/apache/invoked-03-01.xml b/test/samples/apache/invoked-03-01.xml new file mode 100644 index 0000000..3b73a66 --- /dev/null +++ b/test/samples/apache/invoked-03-01.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/invoked-03.xml b/test/samples/apache/invoked-03.xml new file mode 100644 index 0000000..91244e8 --- /dev/null +++ b/test/samples/apache/invoked-03.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/invoker-01.xml b/test/samples/apache/invoker-01.xml new file mode 100644 index 0000000..5355896 --- /dev/null +++ b/test/samples/apache/invoker-01.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/invoker-02.xml b/test/samples/apache/invoker-02.xml new file mode 100644 index 0000000..a6fb2cb --- /dev/null +++ b/test/samples/apache/invoker-02.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/test/samples/apache/invoker-03.xml b/test/samples/apache/invoker-03.xml new file mode 100644 index 0000000..be2d1fa --- /dev/null +++ b/test/samples/apache/invoker-03.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/invoker-04.xml b/test/samples/apache/invoker-04.xml new file mode 100644 index 0000000..81c1bb8 --- /dev/null +++ b/test/samples/apache/invoker-04.xml @@ -0,0 +1,55 @@ + + + + + + + + + foo + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/samples/apache/issue62-01-ext.xml b/test/samples/apache/issue62-01-ext.xml new file mode 100644 index 0000000..8c65a78 --- /dev/null +++ b/test/samples/apache/issue62-01-ext.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/test/samples/apache/issue62-01.xml b/test/samples/apache/issue62-01.xml new file mode 100644 index 0000000..3599e5e --- /dev/null +++ b/test/samples/apache/issue62-01.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/test/samples/apache/issue62-02-ext.xml b/test/samples/apache/issue62-02-ext.xml new file mode 100644 index 0000000..98da5d6 --- /dev/null +++ b/test/samples/apache/issue62-02-ext.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/issue62-02.xml b/test/samples/apache/issue62-02.xml new file mode 100644 index 0000000..8e52e42 --- /dev/null +++ b/test/samples/apache/issue62-02.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/issue62-03-ext.xml b/test/samples/apache/issue62-03-ext.xml new file mode 100644 index 0000000..0168447 --- /dev/null +++ b/test/samples/apache/issue62-03-ext.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/issue62-03.xml b/test/samples/apache/issue62-03.xml new file mode 100644 index 0000000..46b8991 --- /dev/null +++ b/test/samples/apache/issue62-03.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/issue64-01.xml b/test/samples/apache/issue64-01.xml new file mode 100644 index 0000000..bb848fe --- /dev/null +++ b/test/samples/apache/issue64-01.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/issue64-02.xml b/test/samples/apache/issue64-02.xml new file mode 100644 index 0000000..7e44d30 --- /dev/null +++ b/test/samples/apache/issue64-02.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/jsp-rootctx-test.xml b/test/samples/apache/jsp-rootctx-test.xml new file mode 100644 index 0000000..6a71579 --- /dev/null +++ b/test/samples/apache/jsp-rootctx-test.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + diff --git a/test/samples/apache/log-on-config.xml b/test/samples/apache/log-on-config.xml new file mode 100644 index 0000000..b57780e --- /dev/null +++ b/test/samples/apache/log-on-config.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/microwave-01.xml b/test/samples/apache/microwave-01.xml new file mode 100644 index 0000000..86efa26 --- /dev/null +++ b/test/samples/apache/microwave-01.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/microwave-02-legacy.xml b/test/samples/apache/microwave-02-legacy.xml new file mode 100644 index 0000000..eaeb14a --- /dev/null +++ b/test/samples/apache/microwave-02-legacy.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/microwave-02.xml b/test/samples/apache/microwave-02.xml new file mode 100644 index 0000000..c03fe05 --- /dev/null +++ b/test/samples/apache/microwave-02.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/microwave-03.xml b/test/samples/apache/microwave-03.xml new file mode 100644 index 0000000..887b1a6 --- /dev/null +++ b/test/samples/apache/microwave-03.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/microwave-04.xml b/test/samples/apache/microwave-04.xml new file mode 100644 index 0000000..96656dd --- /dev/null +++ b/test/samples/apache/microwave-04.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/microwave-05.xml b/test/samples/apache/microwave-05.xml new file mode 100644 index 0000000..06b9fc9 --- /dev/null +++ b/test/samples/apache/microwave-05.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/parallel-01.xml b/test/samples/apache/parallel-01.xml new file mode 100644 index 0000000..e822285 --- /dev/null +++ b/test/samples/apache/parallel-01.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/parallel-02.xml b/test/samples/apache/parallel-02.xml new file mode 100644 index 0000000..106fc86 --- /dev/null +++ b/test/samples/apache/parallel-02.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/test/samples/apache/parallel-03.xml b/test/samples/apache/parallel-03.xml new file mode 100644 index 0000000..d57196a --- /dev/null +++ b/test/samples/apache/parallel-03.xml @@ -0,0 +1,118 @@ + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/prefix-01.xml b/test/samples/apache/prefix-01.xml new file mode 100644 index 0000000..79cbe69 --- /dev/null +++ b/test/samples/apache/prefix-01.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/test/samples/apache/scxml-initial-attr.xml b/test/samples/apache/scxml-initial-attr.xml new file mode 100644 index 0000000..0965653 --- /dev/null +++ b/test/samples/apache/scxml-initial-attr.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/test/samples/apache/send-01.xml b/test/samples/apache/send-01.xml new file mode 100644 index 0000000..4f3cf40 --- /dev/null +++ b/test/samples/apache/send-01.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + Say yes or no. + + + + + + + This is just an example. + + + + + + + + diff --git a/test/samples/apache/send-02.xml b/test/samples/apache/send-02.xml new file mode 100644 index 0000000..9685385 --- /dev/null +++ b/test/samples/apache/send-02.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/src-test-1.xml b/test/samples/apache/src-test-1.xml new file mode 100644 index 0000000..74e6ce2 --- /dev/null +++ b/test/samples/apache/src-test-1.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/test/samples/apache/src-test-2.xml b/test/samples/apache/src-test-2.xml new file mode 100644 index 0000000..16789ab --- /dev/null +++ b/test/samples/apache/src-test-2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/test/samples/apache/src-test-3.xml b/test/samples/apache/src-test-3.xml new file mode 100644 index 0000000..d675687 --- /dev/null +++ b/test/samples/apache/src-test-3.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + diff --git a/test/samples/apache/src-test-4.xml b/test/samples/apache/src-test-4.xml new file mode 100644 index 0000000..4fe18e5 --- /dev/null +++ b/test/samples/apache/src-test-4.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/test/samples/apache/src-test-5.xml b/test/samples/apache/src-test-5.xml new file mode 100644 index 0000000..a36e112 --- /dev/null +++ b/test/samples/apache/src-test-5.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/test/samples/apache/state-01.xml b/test/samples/apache/state-01.xml new file mode 100644 index 0000000..22a9018 --- /dev/null +++ b/test/samples/apache/state-01.xml @@ -0,0 +1,24 @@ + + + + + + + + + diff --git a/test/samples/apache/stateless-01.xml b/test/samples/apache/stateless-01.xml new file mode 100644 index 0000000..1229b8f --- /dev/null +++ b/test/samples/apache/stateless-01.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/stateless-parallel-01.xml b/test/samples/apache/stateless-parallel-01.xml new file mode 100644 index 0000000..f2e9017 --- /dev/null +++ b/test/samples/apache/stateless-parallel-01.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/static-method.xml b/test/samples/apache/static-method.xml new file mode 100644 index 0000000..7ad034a --- /dev/null +++ b/test/samples/apache/static-method.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + diff --git a/test/samples/apache/stopwatch.xml b/test/samples/apache/stopwatch.xml new file mode 100644 index 0000000..57572a1 --- /dev/null +++ b/test/samples/apache/stopwatch.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/tie-breaker-01.xml b/test/samples/apache/tie-breaker-01.xml new file mode 100644 index 0000000..7cff894 --- /dev/null +++ b/test/samples/apache/tie-breaker-01.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/tie-breaker-02.xml b/test/samples/apache/tie-breaker-02.xml new file mode 100644 index 0000000..62118dd --- /dev/null +++ b/test/samples/apache/tie-breaker-02.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/tie-breaker-03.xml b/test/samples/apache/tie-breaker-03.xml new file mode 100644 index 0000000..d850865 --- /dev/null +++ b/test/samples/apache/tie-breaker-03.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/tie-breaker-04.xml b/test/samples/apache/tie-breaker-04.xml new file mode 100644 index 0000000..df87e63 --- /dev/null +++ b/test/samples/apache/tie-breaker-04.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/tie-breaker-05.xml b/test/samples/apache/tie-breaker-05.xml new file mode 100644 index 0000000..1fcbd35 --- /dev/null +++ b/test/samples/apache/tie-breaker-05.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/tie-breaker-06.xml b/test/samples/apache/tie-breaker-06.xml new file mode 100644 index 0000000..a7f26ff --- /dev/null +++ b/test/samples/apache/tie-breaker-06.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/transitions-01-legacy.xml b/test/samples/apache/transitions-01-legacy.xml new file mode 100644 index 0000000..f46bc74 --- /dev/null +++ b/test/samples/apache/transitions-01-legacy.xml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/transitions-01.xml b/test/samples/apache/transitions-01.xml new file mode 100644 index 0000000..ee5133f --- /dev/null +++ b/test/samples/apache/transitions-01.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/transitions-02.xml b/test/samples/apache/transitions-02.xml new file mode 100644 index 0000000..7c1dcf5 --- /dev/null +++ b/test/samples/apache/transitions-02.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/transitions-03.xml b/test/samples/apache/transitions-03.xml new file mode 100644 index 0000000..303d673 --- /dev/null +++ b/test/samples/apache/transitions-03.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/transitions-04.xml b/test/samples/apache/transitions-04.xml new file mode 100644 index 0000000..547b702 --- /dev/null +++ b/test/samples/apache/transitions-04.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/transitions-05.xml b/test/samples/apache/transitions-05.xml new file mode 100644 index 0000000..7400359 --- /dev/null +++ b/test/samples/apache/transitions-05.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/travel-dialog.xml b/test/samples/apache/travel-dialog.xml new file mode 100644 index 0000000..08ebc99 --- /dev/null +++ b/test/samples/apache/travel-dialog.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/wildcard-01.xml b/test/samples/apache/wildcard-01.xml new file mode 100644 index 0000000..9f7ddc3 --- /dev/null +++ b/test/samples/apache/wildcard-01.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/wildcard-02.xml b/test/samples/apache/wildcard-02.xml new file mode 100644 index 0000000..d039f69 --- /dev/null +++ b/test/samples/apache/wildcard-02.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + diff --git a/test/samples/apache/wizard-01.xml b/test/samples/apache/wizard-01.xml new file mode 100644 index 0000000..87492a6 --- /dev/null +++ b/test/samples/apache/wizard-01.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/apache/wizard-02.xml b/test/samples/apache/wizard-02.xml new file mode 100644 index 0000000..924b83f --- /dev/null +++ b/test/samples/apache/wizard-02.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/samples/w3c/Blackjack.scxml b/test/samples/w3c/Blackjack.scxml new file mode 100644 index 0000000..4f55e53 --- /dev/null +++ b/test/samples/w3c/Blackjack.scxml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/w3c/Main.scxml b/test/samples/w3c/Main.scxml new file mode 100644 index 0000000..4c03631 --- /dev/null +++ b/test/samples/w3c/Main.scxml @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hello World! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/samples/w3c/Test2Sub1.xml b/test/samples/w3c/Test2Sub1.xml new file mode 100644 index 0000000..6ab7b98 --- /dev/null +++ b/test/samples/w3c/Test2Sub1.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/test/samples/w3c/TrafficReport.scxml b/test/samples/w3c/TrafficReport.scxml new file mode 100644 index 0000000..09e2e93 --- /dev/null +++ b/test/samples/w3c/TrafficReport.scxml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/w3c/calc.scxml b/test/samples/w3c/calc.scxml new file mode 100644 index 0000000..e759b45 --- /dev/null +++ b/test/samples/w3c/calc.scxml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/w3c/edit-profile-config.scxml b/test/samples/w3c/edit-profile-config.scxml new file mode 100644 index 0000000..2f8753e --- /dev/null +++ b/test/samples/w3c/edit-profile-config.scxml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/w3c/log-on-config.scxml b/test/samples/w3c/log-on-config.scxml new file mode 100644 index 0000000..01f45d6 --- /dev/null +++ b/test/samples/w3c/log-on-config.scxml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/w3c/microwave-01.scxml b/test/samples/w3c/microwave-01.scxml new file mode 100644 index 0000000..71e2f98 --- /dev/null +++ b/test/samples/w3c/microwave-01.scxml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/w3c/microwave-02.scxml b/test/samples/w3c/microwave-02.scxml new file mode 100644 index 0000000..a96f1fd --- /dev/null +++ b/test/samples/w3c/microwave-02.scxml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/samples/w3c/simple.xml b/test/samples/w3c/simple.xml new file mode 100644 index 0000000..d3badc8 --- /dev/null +++ b/test/samples/w3c/simple.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/test/schema/scxml-message.xsd b/test/schema/scxml-message.xsd new file mode 100644 index 0000000..de4b4b8 --- /dev/null +++ b/test/schema/scxml-message.xsd @@ -0,0 +1,122 @@ + + + + + + + XML Schema for sending messages to SCXML processors. + Version 1.0 + + + + + + + + Group allowing attributes from other namespaces + + + + + + + + + + + + + Non SCXML senders are not required to specify a sendid + + + + + + + Defaults to "external.event" + + + + + + + Defaults to "scxml" + + + + + + + + + + + + + + + + + + + + + + + + + The mime type of the child content. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/schema/scxml.xsd b/test/schema/scxml.xsd new file mode 100644 index 0000000..ebc0654 --- /dev/null +++ b/test/schema/scxml.xsd @@ -0,0 +1,176 @@ + + + + + + This is the XML Schema driver for SCXML 1.0. + Please use this namespace for SCXML 1.0 elements: + + "http://www.w3.org/2005/07/scxml" + + + + + + + This is the XML Schema driver file for SCXML 1.0. + + This schema: + + sets the namespace for SCXML 1.0 + + imports external schemas (xml.xsd) + + imports SCXML common datatypes, attributes and content models + + imports modular schemas + + SCXML 1.0 includes: + + SCXML core constructs + + SCXML executable content + + SCXML data model and manipulation + + SCXML external communications + + This schema is permissive such that it accomodates all + datamodels, but validating documents may contain markup that + is ignored in certain datamodels. + + + + + + + This import brings in the XML namespace attributes + The XML attributes are used by various modules. + + + + + + + + This includes brings in the common attributes for SCXML. + + + + + + + + This includes the common content models. + + + + + + + + This includes brings in the common data types for SCXML. + + + + + + + + This imports the data module for SCXML and redefines the following. + [1] Redefines assign attribute group to allow type and attr + + + + + + + + + + + + + This includes the script module for SCXML. + + + + + + + + This imports the external module for SCXML and redefines the following. + [1] Redefines send and invoke mix group to allow + param + [2] Redefines finalize mix group to allow: + executable content + + + + + + + + + + + + + + + + + + + + + + + + + + This imports the core module for SCXML and redefines the following. + [1] Redefines executable content to allow + send, assign, validate, cancel and script elements + [2] Redefines state and parallel mix group to allow + invoke and datamodel + [3] Redefines scxml group to allow + datamodel and script + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/src/test-apache-commons.cpp b/test/src/test-apache-commons.cpp new file mode 100644 index 0000000..51c275d --- /dev/null +++ b/test/src/test-apache-commons.cpp @@ -0,0 +1,128 @@ +#include "uscxml/Interpreter.h" +#include + +using namespace uscxml; +using namespace Arabica::DOM; +using namespace Arabica::XPath; + +static std::string path; + +bool testEvents1() { + LOG(INFO) << "---- testEvent1 "; + Interpreter* interpreter = new Interpreter(path + "/eventdata-01.xml"); + interpreter->start(); + interpreter->waitForStabilization(); + assert(interpreter->getConfiguration().size() == 1); + assert(Interpreter::isMember(interpreter->getState("state1"), interpreter->getConfiguration())); + + Event eventFoo; + eventFoo.name = "event.foo"; + eventFoo.atom = "3"; + interpreter->receive(eventFoo); + interpreter->waitForStabilization(); + assert(interpreter->getConfiguration().size() == 1); + assert(Interpreter::isMember(interpreter->getState("state3"), interpreter->getConfiguration())); + + Event eventBar; + eventBar.name = "event.bar"; + eventBar.atom = "6"; + interpreter->receive(eventBar); + interpreter->waitForStabilization(); + assert(interpreter->getConfiguration().size() == 1); + assert(Interpreter::isMember(interpreter->getState("state6"), interpreter->getConfiguration())); + + Event eventBaz; + eventBaz.name = "event.baz"; + eventBaz.atom = "7"; + interpreter->receive(eventBaz); + + delete interpreter; + return true; +} + +bool testEvents2() { + LOG(INFO) << "---- testEvent2 "; + Interpreter* interpreter = new Interpreter(path + "/eventdata-02.xml"); + interpreter->start(); + interpreter->waitForStabilization(); + assert(interpreter->getConfiguration().size() == 1); + assert(Interpreter::isMember(interpreter->getState("state0"), interpreter->getConfiguration())); + + Event eventConnAlert; + eventConnAlert.name = "connection.alerting"; + eventConnAlert.atom = "'line2'"; + interpreter->receive(eventConnAlert); + interpreter->waitForStabilization(); + assert(interpreter->getConfiguration().size() == 1); + assert(Interpreter::isMember(interpreter->getState("state2"), interpreter->getConfiguration())); + + Event eventConnAlert2; + eventConnAlert2.name = "connection.alerting"; + eventConnAlert2.compound["line"] = Data(std::string("4")); + interpreter->receive(eventConnAlert2); + + delete interpreter; + return true; +} + +//bool testEvents3() { +// LOG(INFO) << "---- testEvent3 "; +// Interpreter* Interpreter = new Interpreter(path + "/eventdata-03.xml"); +// interpreter->start(); +// interpreter->waitForStabilization(); +// Thread::sleepMs(200); +// assert(interpreter->getConfiguration().size() == 1); +// assert(Interpreter::isMember(interpreter->getState("state0"), interpreter->getConfiguration())); +// +// Event eventConnAlert; +// eventConnAlert.name = "connection.alerting"; +// eventConnAlert.atom = "'line2'"; +// interpreter->receive(eventConnAlert); +// Thread::sleepMs(200); +// assert(interpreter->getConfiguration().size() == 1); +// assert(Interpreter::isMember(interpreter->getState("state2"), interpreter->getConfiguration())); +// +// Event eventConnAlert2; +// eventConnAlert2.name = "connection.alerting"; +// eventConnAlert2.compound["line"] = Data(std::string("4")); +// interpreter->receive(eventConnAlert2); +// Thread::sleepMs(200); +// assert(interpreter->getConfiguration().size() == 1); +// assert(Interpreter::isMember(interpreter->getState("state4"), interpreter->getConfiguration())); +// +// delete Interpreter; +// return true; +//} + + +int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "Expected path to scxml file from apache commons distribution" << std::endl; + exit(EXIT_FAILURE); + } + + path = "file://"; + path += argv[1]; + + if (!testEvents1()) + return EXIT_FAILURE; + if (!testEvents2()) + return EXIT_FAILURE; +// if (!testEvents3()) +// return EXIT_FAILURE; + +// +// Interpreter* scxmlInterpreter = new Interpreter(path + "/tie-breaker-01.xml"); +// SCXMLRunner* scxmlRun = new SCXMLRunner(scxmlInterpreter); +// scxmlRun->start(); +// +// Thread::sleepMs(100); +// assert(Interpreter::isMember(scxmlinterpreter->getState("ten"), scxmlinterpreter->getConfiguration())); +// +// boost::shared_ptr event = boost::shared_ptr(new Event()); +// event->name = "ten.done"; +// scxmlinterpreter->receive(event); +// scxmlRun->join(); +// scxmlinterpreter->receive(event); + +} \ No newline at end of file diff --git a/test/src/test-communication.cpp b/test/src/test-communication.cpp new file mode 100644 index 0000000..97584b8 --- /dev/null +++ b/test/src/test-communication.cpp @@ -0,0 +1,26 @@ +#include "uscxml/Interpreter.h" +#include + +int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "Expected path to test-communication.scxml" << std::endl; + exit(EXIT_FAILURE); + } + + + using namespace uscxml; + std::list _interpreters; + +// Event e; +// e.compound["foo"] = Data("bar", Data::VERBATIM); +// e.compound["foo2"] = Data("bar2", Data::VERBATIM); +// std::cout << e.toDocument() << std::endl; + + for (int i = 0; i < 1; i++) { + _interpreters.push_back(new Interpreter(argv[1])); + _interpreters.back()->start(); + } + + tthread::this_thread::sleep_for(tthread::chrono::milliseconds(100000)); + +} \ No newline at end of file diff --git a/test/src/test-communication.scxml b/test/src/test-communication.scxml new file mode 100644 index 0000000..cc3f577 --- /dev/null +++ b/test/src/test-communication.scxml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/src/test-ecmascript-v8.cpp b/test/src/test-ecmascript-v8.cpp new file mode 100644 index 0000000..d0b69ba --- /dev/null +++ b/test/src/test-ecmascript-v8.cpp @@ -0,0 +1,44 @@ +#include "uscxml/Interpreter.h" +#include "uscxml/datamodel/ecmascript/v8/V8DataModel.h" + +int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "Expected path to test-ecmascript.scxml" << std::endl; + exit(EXIT_FAILURE); + } + + using namespace uscxml; + using namespace Arabica::DOM; + using namespace Arabica::XPath; + +// class SCXMLRunner : public Thread { +// public: +// SCXMLRunner(Runtime* runtime) : _runtime(runtime) {} +// void run() { +// _runtime->interpret(); +// } +// +// Runtime* _runtime; +// }; + +// boost::shared_ptr v8 = boost::static_pointer_cast(Factory::create("datamodel:ecmascript", Arabica::DOM::Node())); +// v8->eval("var x = 4;"); +// assert(v8->evalAsBool("x == 4")); +// assert(!v8->evalAsBool("x == 5")); + + Interpreter* scxml = new Interpreter(argv[1]); + scxml->dump(); +// scxml->interpret(); + scxml->start(); + scxml->waitForStabilization(); + + Event event1; + event1.name = "event1"; + scxml->receive(event1); + scxml->waitForStabilization(); + tthread::this_thread::sleep_for(tthread::chrono::milliseconds(200)); + +// SCXMLRunner* scxmlRun = new SCXMLRunner(scxmlRuntime); +// scxmlRun->start(); + +} \ No newline at end of file diff --git a/test/src/test-ecmascript.scxml b/test/src/test-ecmascript.scxml new file mode 100644 index 0000000..aa88f17 --- /dev/null +++ b/test/src/test-ecmascript.scxml @@ -0,0 +1,112 @@ + + + + + + + { + "id": 1, + "name": "Foo", + "price": 123, + "tags": [ "Bar", "Eek" ], + "stock": { + "warehouse": 300, + "retail": 20, + } + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/src/test-eventdelay.cpp b/test/src/test-eventdelay.cpp new file mode 100644 index 0000000..6a28e44 --- /dev/null +++ b/test/src/test-eventdelay.cpp @@ -0,0 +1,39 @@ +#include "uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h" + +int eventCalled = 0; + +#include + +static void callback(void* userData, const std::string eventId) { +// std::cout << eventId << ": " << (const char*)userData << std::endl; + std::cout << eventId << std::endl << std::flush; + eventCalled++; +} + +int main(int argc, char** argv) { + + using namespace uscxml; + DelayedEventQueue* eq = new DelayedEventQueue(); + + std::cout << "Starting" << std::endl; + eq->start(); + tthread::this_thread::sleep_for(tthread::chrono::milliseconds(10)); + +// eq->addEvent("foo", callback, 200, (void*)"event foo"); +// eq->addEvent("bar", callback, 400, (void*)"event bar"); +// eq->addEvent("bar", callback, 600, (void*)"event bar"); +// eq->cancelEvent("bar"); +// eq->addEvent("bar", callback, 300, (void*)"event bar"); +// eq->addEvent("baz", callback, 400, (void*)"event baz"); + + for (unsigned int i = 0; i <= 5000; i += 500) { +// eq->stop(); + std::stringstream ss; + ss << i; + eq->addEvent(ss.str(), callback, i, NULL); + std::cout << "Added " << i << std::endl; +// eq->start(); + } + tthread::this_thread::sleep_for(tthread::chrono::milliseconds(20000)); + +} \ No newline at end of file diff --git a/test/src/test-execution.cpp b/test/src/test-execution.cpp new file mode 100644 index 0000000..e484eb4 --- /dev/null +++ b/test/src/test-execution.cpp @@ -0,0 +1,16 @@ +#include "uscxml/Interpreter.h" + +int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "Expected path to test-execution.scxml" << std::endl; + exit(EXIT_FAILURE); + } + + using namespace uscxml; + using namespace Arabica::DOM; + using namespace Arabica::XPath; + + Interpreter* interpreter = new Interpreter(argv[1]); + interpreter->dump(); + interpreter->interpret(); +} \ No newline at end of file diff --git a/test/src/test-execution.scxml b/test/src/test-execution.scxml new file mode 100644 index 0000000..ada1a17 --- /dev/null +++ b/test/src/test-execution.scxml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/src/test-predicates.cpp b/test/src/test-predicates.cpp new file mode 100644 index 0000000..73c37dc --- /dev/null +++ b/test/src/test-predicates.cpp @@ -0,0 +1,60 @@ +#define protected public +#include "uscxml/Interpreter.h" +#undef protected + +int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "Expected path to test-predicates.scxml" << std::endl; + exit(EXIT_FAILURE); + } + + using namespace uscxml; + using namespace Arabica::DOM; + using namespace Arabica::XPath; + + Interpreter* interpreter = new Interpreter(argv[1]); + + Node atomicState = interpreter->getState("atomic"); + assert(Interpreter::isAtomic(atomicState)); + assert(!Interpreter::isParallel(atomicState)); + assert(!Interpreter::isCompound(atomicState)); + + Node compoundState = interpreter->getState("compound"); + assert(!Interpreter::isAtomic(compoundState)); + assert(!Interpreter::isParallel(compoundState)); + assert(Interpreter::isCompound(compoundState)); + + Node parallelState = interpreter->getState("parallel"); + assert(!Interpreter::isAtomic(parallelState)); + assert(Interpreter::isParallel(parallelState)); + assert(!Interpreter::isCompound(parallelState)); // parallel states are not compound! + + Node initialState = interpreter->getInitialState(); + assert(initialState == atomicState); + + NodeSet childs = interpreter->getChildStates(compoundState); + Node compundChild1 = interpreter->getState("compundChild1"); + Node compundChild2 = interpreter->getState("compundChild2"); + assert(childs.size() > 0); + assert(Interpreter::isMember(compundChild1, childs)); + assert(Interpreter::isMember(compundChild2, childs)); + assert(!Interpreter::isMember(compoundState, childs)); + + assert(Interpreter::isDescendant(compundChild1, compoundState)); + + std::string transEvents; + transEvents = "error"; + assert(Interpreter::nameMatch(transEvents, "error")); + assert(!Interpreter::nameMatch(transEvents, "foo")); + + transEvents = "error foo"; + assert(Interpreter::nameMatch(transEvents, "error")); + assert(Interpreter::nameMatch(transEvents, "error.send")); + assert(Interpreter::nameMatch(transEvents, "error.send.failed")); + assert(Interpreter::nameMatch(transEvents, "foo")); + assert(Interpreter::nameMatch(transEvents, "foo.bar")); + assert(!Interpreter::nameMatch(transEvents, "errors.my.custom")); + assert(!Interpreter::nameMatch(transEvents, "errorhandler.mistake")); + assert(!Interpreter::nameMatch(transEvents, "errOr.send")); + assert(!Interpreter::nameMatch(transEvents, "foobar")); +} \ No newline at end of file diff --git a/test/src/test-predicates.scxml b/test/src/test-predicates.scxml new file mode 100644 index 0000000..98848a2 --- /dev/null +++ b/test/src/test-predicates.scxml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file -- cgit v0.12