summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Radomski <radomski@tk.informatik.tu-darmstadt.de>2014-03-11 14:45:38 (GMT)
committerStefan Radomski <radomski@tk.informatik.tu-darmstadt.de>2014-03-11 14:45:38 (GMT)
commitc34e0ce034586a05308e552cbbdff48beec7dd96 (patch)
tree39c73ba64b0d228b3c53913ea4e7ab6dda6ad5c1
parentca46aa711fb5d08a8fd1cc6b91593c281189e8e3 (diff)
downloaduscxml-c34e0ce034586a05308e552cbbdff48beec7dd96.zip
uscxml-c34e0ce034586a05308e552cbbdff48beec7dd96.tar.gz
uscxml-c34e0ce034586a05308e552cbbdff48beec7dd96.tar.bz2
Integrated debugger into browser (use -d command line parameter)
-rw-r--r--CMakeLists.txt9
-rw-r--r--README.md6
-rw-r--r--apps/uscxml-browser.cpp19
-rw-r--r--apps/uscxml-debug.cpp81
-rw-r--r--apps/uscxml-debugger.html2832
-rw-r--r--src/uscxml/DOMUtils.cpp23
-rw-r--r--src/uscxml/DOMUtils.h2
-rw-r--r--src/uscxml/Interpreter.cpp45
-rw-r--r--src/uscxml/Interpreter.h36
-rw-r--r--src/uscxml/Message.cpp7
-rw-r--r--src/uscxml/debug/Breakpoint.cpp65
-rw-r--r--src/uscxml/debug/Breakpoint.h29
-rw-r--r--src/uscxml/debug/DebugSession.cpp378
-rw-r--r--src/uscxml/debug/DebugSession.h99
-rw-r--r--src/uscxml/debug/Debugger.cpp114
-rw-r--r--src/uscxml/debug/Debugger.h60
-rw-r--r--src/uscxml/debug/DebuggerServlet.cpp330
-rw-r--r--src/uscxml/debug/DebuggerServlet.h69
-rw-r--r--src/uscxml/plugins/ioprocessor/basichttp/BasicHTTPIOProcessor.cpp2
-rw-r--r--src/uscxml/server/HTTPServer.cpp2
-rw-r--r--test/samples/uscxml/test-dirmon.scxml2
-rw-r--r--test/samples/w3c/draft/calc.scxml158
22 files changed, 3908 insertions, 460 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f1ba3bd..7b2e89e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -937,15 +937,6 @@ if (NOT CMAKE_CROSSCOMPILING)
set_target_properties(uscxml-browser PROPERTIES FOLDER "Apps")
install_executable(TARGETS uscxml-browser COMPONENT tools)
- add_executable(uscxml-debug apps/uscxml-debug.cpp)
- target_link_libraries(uscxml-debug uscxml)
- if (NOT CMAKE_CROSSCOMPILING)
- set_target_properties(uscxml-debug PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE)
-# cotire(uscxml-browser)
- endif()
- set_target_properties(uscxml-debug PROPERTIES FOLDER "Apps")
- install_executable(TARGETS uscxml-debug COMPONENT tools)
-
if (PROTOBUF_FOUND AND OFF)
file(GLOB W3C-MMI-COMMON ${PROJECT_SOURCE_DIR}/apps/w3c-mmi/*.cpp ${PROJECT_SOURCE_DIR}/apps/w3c-mmi/*.h)
diff --git a/README.md b/README.md
index 002f4c0..9ec8b27 100644
--- a/README.md
+++ b/README.md
@@ -44,12 +44,6 @@ uSCXML still fails the following ecmascript tests:
<table>
<tr><th>Test#</th><th>Status</th><th>Description</th><th>Comment</th></tr>
<tr>
- <td><tt><a href="https://github.com/tklab-tud/uscxml/blob/master/test/samples/w3c/ecma/test301.scxml">301</a></tt></td>
- <td><tt>Failed</tt></td>
- <td>"the processor should reject this document because it can't download the script"</td>
- <td>uSCXML continues processing as if there was no <tt>&lt;script></tt> element.</td>
- </tr>
- <tr>
<td><tt><a href="https://github.com/tklab-tud/uscxml/blob/master/test/samples/w3c/ecma/test329.scxml">329</a></tt></td>
<td><tt>Failed</tt></td>
<td>"test that none of the system variables can be modified"</td>
diff --git a/apps/uscxml-browser.cpp b/apps/uscxml-browser.cpp
index 4188132..81d8883 100644
--- a/apps/uscxml-browser.cpp
+++ b/apps/uscxml-browser.cpp
@@ -1,7 +1,7 @@
#include "uscxml/config.h"
#include "uscxml/Interpreter.h"
#include "uscxml/DOMUtils.h"
-#include "uscxml/debug/SCXMLDotWriter.h"
+#include "uscxml/debug/DebuggerServlet.h"
#include <glog/logging.h>
#ifdef HAS_SIGNAL_H
@@ -142,6 +142,12 @@ int main(int argc, char** argv) {
}
HTTPServer::getInstance(options.httpPort, options.wsPort, sslConf);
+ DebuggerServlet* debugger;
+ if (options.withDebugger) {
+ debugger = new DebuggerServlet();
+ HTTPServer::getInstance()->registerServlet("/debug", debugger);
+ }
+
// instantiate and configure interpreters
std::list<Interpreter> interpreters;
std::map<std::string, InterpreterOptions*>::iterator confIter = options.interpreters.begin();
@@ -160,9 +166,8 @@ int main(int argc, char** argv) {
VerboseMonitor* vm = new VerboseMonitor();
interpreter.addMonitor(vm);
}
- if (options.useDot) {
- SCXMLDotWriter* dotWriter = new SCXMLDotWriter();
- interpreter.addMonitor(dotWriter);
+ if (options.withDebugger) {
+ interpreter.addMonitor(debugger);
}
interpreters.push_back(interpreter);
@@ -194,5 +199,11 @@ int main(int argc, char** argv) {
}
}
+ if (options.withDebugger) {
+ // idle and wait for CTRL+C or debugging events
+ while(true)
+ tthread::this_thread::sleep_for(tthread::chrono::seconds(1));
+ }
+
return EXIT_SUCCESS;
} \ No newline at end of file
diff --git a/apps/uscxml-debug.cpp b/apps/uscxml-debug.cpp
deleted file mode 100644
index e155ace..0000000
--- a/apps/uscxml-debug.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-#include "uscxml/config.h"
-#include "uscxml/Interpreter.h"
-#include "uscxml/DOMUtils.h"
-#include "uscxml/UUID.h"
-#include "uscxml/debug/SCXMLDotWriter.h"
-#include "uscxml/debug/Breakpoint.h"
-#include "uscxml/debug/Debugger.h"
-#include "uscxml/debug/DebuggerServlet.h"
-#include <glog/logging.h>
-#include <time.h> // mktime
-
-#include <boost/algorithm/string.hpp>
-#include <map>
-
-#ifdef HAS_SIGNAL_H
-#include <signal.h>
-#endif
-
-#ifdef HAS_EXECINFO_H
-#include <execinfo.h>
-#endif
-
-#ifdef HAS_DLFCN_H
-#include <dlfcn.h>
-#endif
-
-using namespace uscxml;
-
-//class Debugger : public HTTPServlet {
-//public:
-//
-// std::string _url;
-// Interpreter _interpreter;
-//
-// HTTPServer::Request _clientConn; // a request the client renews everytime
-// concurrency::BlockingQueue<Data> _sendQueue; // queue of things we have to return to the client
-//
-// tthread::recursive_mutex _mutex;
-// tthread::condition_variable _resumeCond;
-//
-// std::set<Breakpoint> _breakPoints;
-// std::string _sessionId;
-//
-// DebuggerMonitor _monitor;
-//
-// virtual ~Debugger() {
-// }
-//
-// // callbacks from http requests
-//
-//
-// // helpers
-//
-//
-//
-//
-//};
-
-
-int main(int argc, char** argv) {
- using namespace uscxml;
-
- InterpreterOptions options = InterpreterOptions::fromCmdLine(argc, argv);
- DebuggerServlet debuggerServlet;
-
- // setup logging
- google::InitGoogleLogging(argv[0]);
- google::AddLogSink(&debuggerServlet);
-
- // setup HTTP server
- HTTPServer::getInstance(18088, 18089, NULL);
-
-
- HTTPServer::getInstance()->registerServlet("/", &debuggerServlet);
-
- while(true)
- tthread::this_thread::sleep_for(tthread::chrono::seconds(1));
-
-
- return EXIT_SUCCESS;
-} \ No newline at end of file
diff --git a/apps/uscxml-debugger.html b/apps/uscxml-debugger.html
new file mode 100644
index 0000000..daf63e0
--- /dev/null
+++ b/apps/uscxml-debugger.html
@@ -0,0 +1,2832 @@
+<!doctype html>
+<html>
+<head>
+ <!-- <link href="http://localhost/~sradomski/jsPlumb/font-awesome.css" rel="stylesheet"> -->
+ <!-- <link rel="stylesheet" href="http://localhost/~sradomski/jsPlumb/demo-all.css"> -->
+ <!-- <link rel="stylesheet" href="http://localhost/~sradomski/jsPlumb/demo.css"> -->
+
+ <!-- script src="https://google-code-prettify.googlecode.com/svn/loader/prettify.js?autoload=false"></script -->
+ <!--script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js?autoload=false&amp;lang=xml" defer="defer"></script-->
+
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
+ <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js"></script>
+ <script src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.11.1/jquery.validate.min.js"></script>
+ <script src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.11.1/additional-methods.min.js"></script>
+ <script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
+
+ <script src="http://cdn.jsdelivr.net/ace/1.1.01/min/ace.js" charset="utf-8"></script>
+ <script src="http://vkbeautify.googlecode.com/files/vkbeautify.0.99.00.beta.js" charset="utf-8"></script>
+ <script src="http://cdnjs.cloudflare.com/ajax/libs/typeahead.js/0.9.3/typeahead.min.js" charset="utf-8"></script>
+ <script src="http://dean.edwards.name/base/Base.js" charset="utf-8"></script>
+ <!-- alternatively http://maxailloud.github.io/confirm-bootstrap/#liveDemo -->
+
+ <script src="https://rawgithub.com/jibe914/Bootstrap-Confirmation/master/bootstrap3-confirmation.js" charset="utf-8"></script>
+ <!--script src="https://raw.githubusercontent.com/jibe914/Bootstrap-Confirmation/master/bootstrap3-confirmation.js" charset="utf-8"></script-->
+ <!-- script src="https://raw.github.com/Tavicu/bootstrap-confirmation/master/bootstrap-confirmation.js" charset="utf-8"></script -->
+
+
+ <script src="https://rawgithub.com/msurguy/ladda-bootstrap/master/dist/spin.js" charset="utf-8"></script>
+ <script src="https://rawgithub.com/msurguy/ladda-bootstrap/master/dist/ladda.js" charset="utf-8"></script>
+ <script src="https://rawgithub.com/prettycode/Object.identical.js/master/Object.identical.js" charset="utf-8"></script>
+
+
+ <!-- <link rel="stylesheet" href="jQuery-File-Upload/css/style.css">
+ <link rel="stylesheet" href="jQuery-File-Upload/css/jquery.fileupload.css"> -->
+
+
+
+ <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/themes/smoothness/jquery-ui.css" />
+ <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
+ <!-- <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css"> -->
+ <!-- <link rel="stylesheet" href="http://twitter.github.io/typeahead.js/css/main.css" /> -->
+ <link rel="stylesheet" href="https://raw.githubusercontent.com/hyspace/typeahead.js-bootstrap3.less/master/typeahead.css" />
+ <link rel="stylesheet" href="https://rawgithub.com/msurguy/ladda-bootstrap/master/dist/ladda-themeless.min.css" />
+
+ <!-- script src="jquery-ui-1.10.4.uscxml/js/jquery-1.10.2.js"></script>
+ <script src="http://cdnjs.cloudflare.com/ajax/libs/select2/3.4.5/select2.min.js" charset="utf-8"></script>
+ <link rel="stylesheet" href="http://twitter.github.io/typeahead.js/css/main.css" />
+ <script src="Base.js"></script>
+ <script src="jquery-ui-1.10.4.uscxml/js/jquery-ui-1.10.4.custom.js"></script>
+ <script src="http://localhost/~sradomski/ace/ace.js" charset="utf-8"></script>
+ <script src="http://localhost/~sradomski/vkbeautify.0.99.00.beta.js" charset="utf-8"></script>
+ <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/select2/3.4.5/select2.min.css">
+
+ <script src='http://localhost/~sradomski/jsPlumb/jquery.min.js'></script>
+ <script src='http://localhost/~sradomski/jsPlumb/jquery-ui.min.js'></script>
+ <script src='http://localhost/~sradomski/jsPlumb/jquery.ui.touch-punch.min.js'></script>
+ <script src="//cdnjs.cloudflare.com/ajax/libs/ace/1.1.01/ace.js"></script>
+ <script src='http://localhost/~sradomski/jsPlumb/jquery.jsPlumb-1.5.5.js'></script>
+ <script src="http://localhost/~sradomski/ace/ace.js" charset="utf-8"></script>
+ <script src="http://localhost/~sradomski/jquery.dropdown.min.js" charset="utf-8"></script>
+ <script src="http://localhost/~sradomski/bootstrap/js/bootstrap.min.js"></script -->
+
+ <!-- link href="jquery.dropdown.css" rel="stylesheet" / -->
+ <!-- <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/dark-hive/jquery-ui.css"> -->
+ <!-- link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css" -->
+
+ <style type="text/css">
+
+ body {
+/* font-family: "Trebuchet MS", "Helvetica", "Arial", "Verdana", "sans-serif";*/
+ }
+
+ .windowlet {
+ position: absolute;
+ top: 3em;
+ left: 5em;
+ box-shadow: 5px 2px 19px #aaa;
+ -o-box-shadow: 5px 2px 19px #aaa;
+ -webkit-box-shadow: 5px 2px 19px #aaa;
+ -moz-box-shadow: 5px 2px 19px #aaa;
+ }
+
+ .windowlet .panel-body {
+ padding: 8px;
+ }
+
+ .windowlet textarea {
+ resize:vertical;
+ }
+
+ .debug {
+ position:fixed !important;
+ width: 320px;
+ min-width: 250px;
+ }
+
+ .debug > .panel > .panel-group {
+/* overflow: hidden;*/
+ margin-bottom: 0px;
+ }
+
+ .debug > .panel {
+ margin-bottom: 0;
+ }
+
+ .debug > .panel > .panel-footer {
+ padding: 5px 5px;
+ font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
+ font-size: 10px;
+ }
+
+ .panel-group .panel {
+/* border: 1px solid transparent;*/
+ overflow: visible;
+ }
+
+ .debug-header {
+ cursor: move;
+/* padding: 10px 15px 5px 15px;
+*/ }
+
+ .subtitle {
+ font-size: 0.7em;
+ font-weight: normal;
+ }
+
+ /* nested panels */
+ .panel .panel .panel-heading {
+ padding: 8px 15px;
+ }
+
+ .panel .panel-group .panel {
+ border-radius: 0px;
+ }
+
+ .panel .panel .panel-title {
+ font-size: 100%; /* Override explicit font-size from bootstrap */
+ }
+
+ .panel .panel {
+ font-size: 90%;
+ }
+
+ .panel-group .panel+.panel {
+ margin-top: 0px;
+ }
+
+ .panel-heading {
+ position: relative;
+ }
+ .heading-decoration {
+ position: absolute;
+ right: 40px;
+ top: 4px;
+ }
+
+ .heading-decoration .badge {
+ float: left;
+ margin-top: 2px;
+ margin-right: 5px;
+ }
+
+ .panel-heading .panel-toggle:after {
+ /* symbol for "opening" panels */
+ font-family: 'Glyphicons Halflings'; /* essential for enabling glyphicon */
+ content: "\e114"; /* adjust as needed, taken from bootstrap.css */
+ float: right; /* adjust as needed */
+ color: grey; /* adjust as needed */
+ }
+ .panel-heading.collapsed .panel-toggle:after {
+ /* symbol for "collapsed" panels */
+ content: "\e080"; /* adjust as needed, taken from bootstrap.css */
+ }
+
+ /* see http://stackoverflow.com/questions/18059161/css-issue-on-twitter-typeahead-with-bootstrap-3 */
+ .twitter-typeahead .tt-hint {
+ display: block;
+ height: 34px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.428571429;
+ border: 1px solid transparent;
+ }
+
+ .form-group .twitter-typeahead {
+ width: 100%;
+ }
+
+ .messages-content textarea.messages {
+ font-size: 10px;
+ cursor: auto;
+ white-space: pre;
+ overflow: auto;
+ font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
+ height: 16em;
+ }
+
+ ul.dropdown-menu li.server-session {
+ padding-left: 0.7em;
+ }
+
+ ul.recent-documents span.recent-document-features {
+ padding-left: 0.7em;
+ }
+
+ .tooltip-inner {
+ background-color: #333;
+ }
+ .tooltip.bottom .tooltip-arrow {
+ border-bottom-color: #333;
+ background-color: #FFF transparent;
+ }
+
+ .dom {
+ font-size: 10px;
+ font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
+ color: #666;
+ }
+
+ .dom .active-state {
+ color: #000;
+ }
+
+ .dom .xpath-selected {
+ color: #A00;
+ font-weight: bold;
+ }
+
+ </style>
+
+ <script type="text/javascript">
+
+ var SCXMLEditor = Base.extend({
+ constructor: function(params) {
+ $.extend(this, params);
+
+ this.debugger = new SCXMLEditor.Debugger.USCXML({
+ debuggerURL: "http://localhost:8088",
+ scxmlEditor: this,
+ });
+
+ this.xmlView = new SCXMLEditor.XMLView({
+ destNode: $("body").find(".dom")[0],
+ });
+
+ $(this.debugger.getNode()).appendTo($("body"));
+ },
+
+ scxmlDOM: undefined,
+ scxmlURL: undefined,
+ scxmlRoot: undefined,
+ scxmlStates: {},
+
+ setDocumentNameFromURL: function(url) {
+ var tmp = $('<a>', { href:url } )[0];
+ $(this.debugger.debugDocumentNameNode).html(tmp.pathname.split('/').pop());
+ $(this.debugger.saveBreakpointsNode).closest("li").removeClass("disabled");
+ },
+
+ loadURL: function(scxmlURL) {
+ this.scxmlURL = scxmlURL;
+ this.setDocumentNameFromURL(scxmlURL);
+
+ if (scxmlURL in this.debugger.recentDocuments) {
+ // load breakpoints
+ if ('breakpoints' in this.debugger.recentDocuments[scxmlURL]) {
+ for (var index in this.debugger.recentDocuments[scxmlURL].breakpoints) {
+ var breakpointData = this.debugger.recentDocuments[scxmlURL].breakpoints[index];
+ var breakpoint = new SCXMLEditor.Debugger.Breakpoint({
+ data: breakpointData
+ });
+
+ var serializedBreakpoint = JSON.stringify(breakpoint.toWireFormat());
+ var alreadyExists = false;
+
+ // check if such a breakpoint already exists
+ for (var index in this.debugger.breakpoints) {
+ var otherBreakpoint = this.debugger.breakpoints[index];
+ if (serializedBreakpoint === JSON.stringify(otherBreakpoint.toWireFormat())) {
+ alreadyExists = true;
+ break;
+ }
+ }
+
+ if (!alreadyExists)
+ this.debugger.addBreakpoint(breakpoint);
+ }
+ }
+ }
+
+ // get instance into scope chain
+ var scxmlEdit = this;
+ $.ajax({
+ type: "GET",
+ dataType: "text xml",
+ url: scxmlURL,
+ success: function (dom) {
+ scxmlEdit.loadDOM(dom);
+ },
+ error: function(data) {
+ console.log(data);
+ }
+ });
+ },
+
+ loadDOM: function(scxmlDOM) {
+ this.scxmlDOM = scxmlDOM;
+ this.scxmlRoot = $(scxmlDOM).find("scxml")[0];
+
+ // notify the debugger
+ this.debugger.loadDOM(scxmlDOM);
+
+ // var domText = SCXMLEditor.xmlToString(this.scxmlDOM);
+ // $("body").find("pre.dom").text(domText);
+ if (this.xmlView) {
+ this.xmlView.setXML(this.scxmlDOM);
+ this.xmlView.prettyPrint();
+ }
+
+ // add 'nested' class to states of nested scxml documents
+ $(this.scxmlRoot).find("scxml").find("state, final, parallel, history").addClass("nested");
+ $(this.getNode()).appendTo(this.containerNode);
+ },
+
+ getNode: function() {
+ this.node = document.createElement('div');
+ this.node.className = "scxml-editor";
+ this.node.innerHTML = '\
+ <div class="state-tree"></div>\
+ <div class="tools"></div>\
+ ';
+
+ this.stateTreeNode = $(this.node).children("div.state-tree");
+ this.toolsNode = $(this.node).children("div.tools");
+
+ // var rootState = new SCXMLEditor.PortletStateView({
+ // scxmlState: this.scxmlRoot,
+ // scxmlEditor: this
+ // });
+ // this.rootNode = rootState.getNode();
+ // $(this.rootNode).appendTo(this.stateTreeNode);
+ // $(this.rootNode).draggable();
+
+ return this.node;
+ },
+
+ getAllChildStates: function() {
+ return $(this.scxmlRoot).find("state, parallel, final, history").not(".nested");
+ },
+
+ getAllStateIds: function() {
+ var stateIds = [];
+ var self = this;
+ $.each(this.getAllChildStates(), function(index, state) {
+ stateIds.push(SCXMLEditor.getStateId(state));
+ });
+ return stateIds;
+ }
+
+ });
+
+ SCXMLEditor.getStateId = function(state) {
+ return $(state).attr("id") || $(state).attr("name") || "scxml";
+ }
+
+ SCXMLEditor.getStateType = function(state) {
+ return state.nodeName.toLowerCase();
+ }
+
+ SCXMLEditor.xmlToString = function (xmlNode) {
+ var text = xmlNode.xml || (new XMLSerializer()).serializeToString(xmlNode);
+ return vkbeautify.xml(text);
+ }
+
+ SCXMLEditor.XMLView = Base.extend({
+ constructor: function(params) {
+ $.extend(this, params);
+ },
+
+ prettyPrint: function(destNode) {
+ var self = this;
+
+ function escapeText(text) {
+ return $('<div/>').text(text).html();
+ }
+ function prettyIndent(xmlNode, indentation) {
+ var outputText = '';
+ var indenter = '';
+
+ for (var i = 0; i < indentation; i++) {
+ indenter += "&nbsp;&nbsp;&nbsp;&nbsp;";
+ }
+ var isActive = false;
+ var isXPathSelected = false;
+
+ switch(xmlNode.nodeType) {
+ case Node.ELEMENT_NODE:
+ if (xmlNode.nodeName.toLowerCase() === "state") {
+ var stateId = xmlNode.getAttribute("id");
+ if ($.inArray(stateId, self.activeStates) >= 0) {
+ outputText += '<span class="active-state">';
+ isActive = true;
+ }
+ }
+
+ if (xpathNodeSet && xpathNodeSet.snapshotLength > 0) {
+ for (var i = 0; i < xpathNodeSet.snapshotLength; i++) {
+ var currNode = xpathNodeSet.snapshotItem(i);
+ // console.log("Comparing:");
+ // console.log(currNode);
+ // console.log(xmlNode);
+ if (currNode === xmlNode) {
+ // console.log("currNode:");
+ // console.log(currNode);
+ outputText += '<span class="xpath-selected">';
+ isXPathSelected = true;
+ break;
+ }
+ }
+ }
+
+ outputText += indenter;
+ outputText += '&lt;' + xmlNode.nodeName;
+ for (var j = 0; j < xmlNode.attributes.length; j++) {
+ var attribute = xmlNode.attributes.item(j);
+ outputText += " " + attribute.nodeName + "=\"" + attribute.nodeValue + "\"";
+ }
+ if (!xmlNode.hasChildNodes()) {
+ outputText += ' /&gt;';
+ outputText += '<br />';
+ } else {
+ outputText += '&gt;';
+ outputText += '<br />';
+ }
+ break;
+ case Node.COMMENT_NODE:
+ outputText += indenter;
+ outputText += '&lt;!-- ' + escapeText(xmlNode.nodeValue.trim()) + ' -->';
+ outputText += '<br />';
+ break;
+ case Node.TEXT_NODE:
+ var text = xmlNode.nodeValue.trim();
+ if (text.length > 0) {
+ outputText += indenter;
+ outputText += escapeText(text);
+ outputText += '<br />';
+ }
+ break;
+ case Node.ATTRIBUTE_NODE:
+ case Node.CDATA_SECTION_NODE:
+ case Node.ENTITY_REFERENCE_NODE:
+ case Node.ENTITY_NODE:
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ case Node.DOCUMENT_NODE:
+ case Node.DOCUMENT_TYPE_NODE:
+ case Node.DOCUMENT_FRAGMENT_NODE:
+ case Node.NOTATION_NODE:
+ default:
+ }
+
+ var child = xmlNode.firstChild;
+ while(child) {
+ outputText += prettyIndent(child, indentation + 1);
+ child = child.nextSibling;
+ }
+
+ switch(xmlNode.nodeType) {
+ case Node.ELEMENT_NODE:
+ if (xmlNode.hasChildNodes()) {
+ outputText += indenter;
+ outputText += '&lt;/' + xmlNode.nodeName;
+ outputText += '&gt;';
+ outputText += '<br />';
+ }
+ break;
+ default:
+ }
+
+ if (isActive) {
+ outputText += '</span>';
+ }
+ if (isXPathSelected) {
+ outputText += '</span>';
+ }
+
+ return outputText;
+ }
+
+ if (typeof this.xmlNode == "string") {
+ this.xmlNode = $.parseXML(this.xmlNode);
+ }
+
+ // this.xpath = "//*[local-name() = \"state\"][@id=\"s0\"]/*[local-name() = \"onentry\"][1]/*[local-name() = \"foreach\"][1]";
+ // this.xpath = "//*[local-name() = \"state\"][@id=\"s0\"]/*[local-name() = \"onentry\"][1]/*[local-name() = \"foreach\"][1]";
+ var xpathNodeSet = undefined;
+
+ // console.log("xpath:");
+ // console.log(this.xpath);
+
+ if (this.xpath) {
+ try {
+ xpathNodeSet = this.xmlNode.evaluate(
+ this.xpath,
+ this.xmlNode,
+ function (prefix) {
+ switch (prefix) {
+ case 'scxml':
+ default:
+ return 'http://www.w3.org/2005/07/scxml';
+ }
+ return null;
+ },
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
+ null);
+ for (var i = 0; i < xpathNodeSet.snapshotLength; i++) {
+ var currNode = xpathNodeSet.snapshotItem(i);
+ // console.log("Got node:");
+ // console.log(currNode);
+ }
+
+ } catch(e) {
+ console.log(e);
+ }
+ }
+
+
+ var xmlMarkup = '<font color=#888>' + prettyIndent(this.xmlNode, 0) + '</font>';
+
+
+ if (destNode) {
+ $(destNode).html(xmlMarkup);
+ } else {
+ $(this.destNode).html(xmlMarkup);
+ }
+ },
+
+ setDestinationNode: function(destNode) {
+ this.destNode = destNode;
+ },
+
+ setXML: function(xmlNode) {
+ this.xmlNode = xmlNode;
+ },
+
+ setElementXPath: function(xpath) {
+ this.xpath = xpath;
+ },
+
+ setActiveStates: function(states) {
+ this.activeStates = states;
+ },
+ });
+
+ // base class of all views onto a state
+ SCXMLEditor.StateView = Base.extend({
+ constructor: function(params) {
+ $.extend(this, params);
+ this.scxmlStateType = SCXMLEditor.getStateType(this.scxmlState);
+ this.scxmlStateId = SCXMLEditor.getStateId(this.scxmlState);
+
+ $(this.scxmlState).bind("DOMSubtreeModified", this.scxmlStateSubtreeModified)
+ },
+
+ node: undefined,
+ scxmlStateType: undefined,
+ scxmlStateId: undefined,
+ scxmlState: undefined,
+
+ getNode: function() {
+ console.log("SCXMLEditor.StateView.getNode()");
+ },
+
+ scxmlStateSubtreeModified: function() {
+ console.log("SCXMLEditor.StateView.scxmlStateSubtreeModified()");
+ },
+
+ isRoot: function() {
+ return $(this.scxmlState).prop("tagName").toLowerCase() === "scxml";
+ },
+
+ getDirectChildStates: function() {
+ return $(this.scxmlState).children("state, parallel, final, history").not(".nested");
+ },
+
+ getAllChildStates: function() {
+ return $(this.scxmlState).find("state, parallel, final, history").not(".nested");
+ },
+
+ createTransitions: function() {
+
+ }
+ });
+
+ // view a state in a small windowlet
+ SCXMLEditor.ToolsStateView = SCXMLEditor.StateView.extend({
+ constructor: function(params) {
+ this.base(params);
+ },
+
+ getNode: function() {
+
+ var tools = this;
+
+ this.node = document.createElement('div');
+ this.node.className = 'windowlet tools ui-widget-content';
+ this.node.innerHTML = '\
+ <div class="ui-widget-header">' + this.getStateId() + '<span class="ui-icon ui-icon-close"></span></div>\
+ ';
+
+ $(this.node).children( ".ui-widget-header" ).children( ".ui-icon-close" ).click(function() {
+ $(tools.node).remove();
+ });
+
+ $(this.node).resizable();
+ $(this.node).draggable();
+ $(this.node).css("position", "absolute");
+ $(this.node).appendTo(this.containerNode);
+
+ return this.node;
+ },
+
+ scxmlStateSubtreeModified: function() {
+ console.log("SCXMLEditor.StateView.scxmlStateSubtreeModified()");
+ },
+
+ });
+
+ SCXMLEditor.PortletStateView = SCXMLEditor.StateView.extend({
+ constructor: function(params) {
+ this.base(params);
+ },
+
+ getNode: function() {
+
+ this.node = document.createElement('div');
+ this.node.className = 'portlet';
+
+ this.node.innerHTML = '\
+ <div class="portlet-header"><span class="stateId">' + this.scxmlStateId + '</span></div>\
+ <div class="portlet-content"></div>\
+ ';
+
+ this.portletHeader = $(this.node).children("div.portlet-header")[0];
+ this.portletContent = $(this.node).children("div.portlet-content")[0];
+
+ var scxmlState = this.scxmlState;
+
+ var settingsButton = document.createElement('button');
+ settingsButton.innerHTML = 'Settings';
+ $(settingsButton).button({
+ icons: {
+ primary: "ui-icon-gear",
+ secondary: "ui-icon-triangle-1-s"
+ },
+ text: false
+ });
+
+ var settingsMenu = document.createElement('ul');
+ settingsMenu.innerHTML = '\
+ <li><a href="#"><span class="ui-icon ui-icon-plus"></span>Add nested State</a></li>\
+ <li><a href="#"><span class="ui-icon ui-icon-arrow-4-diag"></span>Move this State</a></li>\
+ <li><a href="#"><span class="ui-icon ui-icon-trash"></span>Remove this State</a></li>\
+ <li>-</li>\
+ <li><a href="#"><span class="ui-icon ui-icon-transfer-e-w"></span>Transitions</a></li>\
+ <li><a href="#"><span class="ui-icon ui-icon-gear"></span>Executable Content</a></li>\
+ <li><a href="#"><span class="ui-icon ui-icon-tag"></span>Datamodel</a></li>\
+ <li><a href="#"><span class="ui-icon ui-icon-document"></span>Invokers</a></li>\
+ <li><a href="#"><span class="ui-icon ui-icon-zoomin"></span>States</a></li>\
+ ';
+
+ $(settingsMenu).menu().hide();
+
+ $(settingsButton).click(function() {
+ $(settingsMenu).show().position({
+ my: "left top",
+ at: "left top",
+ of: this
+ });
+ $(settingsMenu).find("a").click(function event() {
+ $(settingsMenu).hide();
+ });
+ $(settingsMenu).mouseleave(function(event) {
+ $(settingsMenu).hide();
+ });
+ return false;
+ })
+
+ $(settingsMenu).prependTo(this.portletHeader);
+ $(settingsButton).prependTo(this.portletHeader);
+
+ var childs = this.getDirectChildStates();
+ var nrChilds = childs.length + 1;
+
+ var nrCols = Math.ceil(Math.sqrt(nrChilds));
+ var nrRows = Math.ceil(nrChilds / nrCols);
+
+ // console.log(nrCols + ' / ' + nrRows);
+ for (var col = 0; col < nrCols; col++) {
+ var colNode = document.createElement('div');
+ colNode.className = "column";
+
+ for (var row = 0; row < nrRows; row++) {
+ if (childs.length > col * nrRows + row) {
+ var child = childs[col * nrRows + row];
+ var childPortlet = new SCXMLEditor.PortletStateView({
+ scxmlState: child,
+ scxmlEditor: this.scxmlEditor
+ });
+ $(childPortlet.getNode()).appendTo(colNode);
+ }
+ }
+ $(colNode).appendTo(this.portletContent);
+
+ $(colNode).sortable({
+ connectWith: ".column",
+ handle: ".portlet-header",
+ cancel: ".portlet-toggle",
+ placeholder: "portlet-placeholder ui-corner-all"
+ });
+ }
+
+ // see https://api.jqueryui.com/theming/icons/
+ $(this.node)
+ .addClass( "ui-widget ui-widget-content ui-helper-clearfix ui-corner-all" )
+ .children( ".portlet-header" )
+ .addClass( "ui-widget-header ui-corner-all" )
+ .prepend( "<span class='ui-icon ui-icon-minusthick portlet-toggle'></span>")
+ .prepend( "<span class='ui-icon ui-icon-newwin portlet-detach'></span>")
+ .append( "&nbsp;&nbsp;&nbsp;");
+
+ $(this.node).children( ".portlet-header" ).children( ".portlet-toggle" ).click(function() {
+ var icon = $(this);
+ icon.toggleClass( "ui-icon-minusthick ui-icon-plusthick" );
+ icon.closest( ".portlet" ).children( ".portlet-content" ).toggle();
+ });
+
+ $(this.node).children( ".portlet-header" ).children( ".portlet-detach" ).click(function() {
+ var detach = $(this);
+ detach.toggleClass( "ui-icon-arrowthickstop-1-s ui-icon-newwin" );
+ this.tools = new SCXMLEditor.ToolsStateView({
+ scxmlState: scxmlState,
+ scxmlEditor: this.scxmlEditor
+ });
+ $(this.tools.getNode()).appendTo($("body")); // TODO ought to be relative?
+ });
+ return this.node;
+
+ },
+ })
+
+ SCXMLEditor.Debugger = Base.extend({
+ constructor: function(params) {
+ this.breakpointsEnabled = true;
+ $.extend(this, params);
+ },
+
+ breakpoints: [],
+ currEvent: {},
+
+ loadDOM: function(scxmlDOM) {
+ // called by the editor, inherit to e.g. activate buttons
+ },
+
+ getNode: function() {
+
+ var debug = this;
+ this.node = document.createElement('div');
+ this.node.className = 'windowlet debug';
+ $(this.node).css("position", "absolute");
+
+ // data-parent="#accordion" missing from <a> to show multiple panels
+ this.node.innerHTML = '\
+ <div class="panel panel-default">\
+ <div class="panel-heading debug-header">\
+ <h4 class="panel-title">Debugger<span class="toolbar pull-right"></span></h4>\
+ <!--span class="subtitle document-name">No Document loaded</span -->\
+ </div>\
+ <div class="panel-group">\
+<!-- Breakpoints -->\
+ <div class="breakpoints panel panel-default">\
+ <div class="panel-heading" data-toggle="collapse" data-target="#collapseBreakpoints">\
+ <span class="panel-toggle"></span>\
+ <h4 class="panel-title">Breakpoints<br/><span class="subtitle current-breakpoint"></span>\
+ <i class="heading-decoration pull-right">\
+ <div class="btn-group">\
+ <button title="Deactivate all Breakpoints" class="deactivate btn btn-default btn-xs btn-primary"><span class="glyphicon glyphicon-off"></span></button>\
+ </div>\
+ <div class="btn-group">\
+ <button title="Add a new Breakpoint" class="new btn btn-default btn-xs"><span class="glyphicon glyphicon-plus"></span></button>\
+ <button \
+ title="Remove all Breakpoints" class="delete-all btn btn-default btn-xs"><span class="glyphicon glyphicon-remove"></span></button>\
+ </div>\
+ </i>\
+ </h4>\
+ </div>\
+ <div id="collapseBreakpoints" class="breakpoints-content panel-collapse collapse">\
+ </div>\
+ </div>\
+<!-- Current Event -->\
+ <!--div class="event panel panel-default">\
+ <div class="panel-heading" data-toggle="collapse" data-target="#collapseEvent">\
+ <h4 class="panel-title">Current Event\
+ <i class="heading-decoration pull-right">\
+ <div class="btn-group">\
+ <button title="Insert a new Event" class="new btn btn-default btn-xs"><span class="glyphicon glyphicon-plus"></span></button>\
+ <button title="Edit current Event" class="edit btn btn-default btn-xs"><span class="glyphicon glyphicon-pencil"></span></button>\
+ <button title="View Event in Window" class="view btn btn-default btn-xs"><span class="glyphicon glyphicon-search"></span></button>\
+ </div>\
+ </i>\
+ <span class="panel-toggle"></span></h4>\
+ </div>\
+ <div id="collapseEvent" class="panel-collapse collapse">\
+ <div class="panel-body event-content"></div>\
+ </div>\
+ </div-->\
+<!-- Active States -->\
+ <!-- div class="states panel panel-default">\
+ <div class="panel-heading" data-toggle="collapse" data-target="#collapseStates">\
+ <h4 class="panel-title">Active States<span class="panel-toggle"></span></h4>\
+ </div>\
+ <div id="collapseStates" class="panel-collapse collapse">\
+ <div class="panel-body states-content">\
+ Anim pariatur cliche reprehenderit, enim eiusmod high life\
+ </div>\
+ </div>\
+ </div -->\
+<!-- Datamodel -->\
+ <div class="datamodel panel panel-default">\
+ <div class="panel-heading" data-toggle="collapse" data-target="#collapseDatamodel">\
+ <h4 class="panel-title">Datamodel\
+ <!--i class="heading-decoration pull-right">\
+ <div class="btn-group">\
+ <button title="Clear Log" class="clear btn btn-default btn-xs"><span class="glyphicon glyphicon-trash"></span></button>\
+ <button title="View Log in Window" class="view btn btn-default btn-xs"><span class="glyphicon glyphicon-search"></span></button>\
+ </div>\
+ </i-->\
+ <span class="panel-toggle"></span></h4>\
+ </div>\
+ <div id="collapseDatamodel" class="panel-collapse collapse">\
+ <div class="panel-body datamodel-content"></div>\
+ </div>\
+ </div>\
+<!-- Assets -->\
+ <!--div class="assets panel panel-default">\
+ <div class="panel-heading" data-toggle="collapse" data-target="#collapseAssets">\
+ <h4 class="panel-title">Assets\
+ <i class="heading-decoration pull-right">\
+ <div class="btn-group">\
+ <button title="New Asset" class="view btn btn-default btn-xs"><span class="glyphicon glyphicon-plus"></span></button>\
+ </div>\
+ </i>\
+ <span class="panel-toggle"></span></h4>\
+ </div>\
+ <div id="collapseAssets" class="panel-collapse collapse">\
+ <div class="panel-body assets-content"></div>\
+ </div>\
+ </div-->\
+<!-- Message -->\
+ <div class="messages panel panel-default">\
+ <div class="panel-heading" data-toggle="collapse" data-target="#collapseMessages">\
+ <h4 class="panel-title">Messages\
+ <i class="heading-decoration pull-right">\
+ <span class="badge"></span>\
+ <div class="btn-group">\
+ <button title="Clear Log" class="clear btn btn-default btn-xs"><span class="glyphicon glyphicon-trash"></span></button>\
+ <button title="Text Wrap" class="textwrap btn btn-default btn-xs"><span class="glyphicon glyphicon-align-left"></span></button>\
+ </div>\
+ </i>\
+ <span class="panel-toggle"></span></h4>\
+ </div>\
+ <div id="collapseMessages" class="panel-collapse collapse in">\
+ <div class="panel-body messages-content">\
+ <textarea class="form-control messages" wrap="off" readonly></textarea>\
+ </div>\
+ </div>\
+ </div>\
+ </div>\
+ <div class="panel-footer">\
+ <span class="status-indicators"></span>\
+ <span class="pull-right document-name"></span>\
+ </div>\
+ </div>\
+ ';
+
+ $(this.node).find("div.panel-collapse").not(".in").prev().addClass("collapsed");
+
+
+ // get some nodes
+ this.debugFooterNode = $(this.node).find(".panel-footer")[0];
+ this.statusIndicatorNode = $(this.node).find(".status-indicators")[0];
+ this.debugDocumentNameNode = $(this.node).find(".document-name")[0];
+ this.currentBreakpointNode = $(this.node).find(".current-breakpoint")[0];
+ this.toolbarNode = $(this.node).find(".toolbar")[0];
+
+ this.messagesNode = $(this.node).find("div.messages-content")[0];
+ this.messagesBadgeNode = $(this.node).find("div.messages").find("span.badge")[0];
+ this.clearMessagesButton = $(this.node).find("div.messages").find("button.clear")[0];
+ this.textwrapMessagesButton = $(this.node).find("div.messages").find("button.textwrap")[0];
+ this.messagesTextarea = $(this.node).find("div.messages-content").find("textarea")[0];
+
+ this.breakpointNode = $(this.node).find("div.breakpoints-content")[0];
+ this.deleteAllBreakpointsButton = $(this.node).find("div.breakpoints").find("button.delete-all")[0];
+ this.disableBreakpointsButton = $(this.node).find("div.breakpoints").find("button.deactivate")[0];
+ this.addNewBreakpointsButton = $(this.node).find("div.breakpoints").find("button.new")[0];
+
+ this.currEventNode = $(this.node).find("div.event-content")[0];
+ this.insertEventButton = $(this.node).find("div.event").find("button.new")[0];
+ this.editEventButton = $(this.node).find("div.event").find("button.edit")[0];
+ this.viewEventButton = $(this.node).find("div.event").find("button.view")[0];
+
+ this.dataModelNode = $(this.node).find("div.datamodel-content")[0];
+ this.clearDataModelLogButton = $(this.node).find("div.datamodel").find("button.clear")[0];
+ this.viewDataModelLogButton = $(this.node).find("div.datamodel").find("button.view")[0];
+
+ this.initBreakpoints();
+ this.initToolBar();
+// this.initCurrEvent();
+ this.initDataModel();
+ this.initMessages();
+
+ // enable tooltips
+ $(this.node).find("button").tooltip({
+ placement: 'bottom',
+ container: 'body',
+ delay: { show: 300, hide: 100 }
+ });
+
+ $(this.node).resizable({grid: [1, 100000]});
+ $(this.node).draggable({
+ handle: $(this.node).find("div.debug-header")
+ });
+
+ return this.node;
+ },
+
+ initMessages: function() {
+ var debug = this;
+
+ $(this.clearMessagesButton).prop('disabled', true);
+
+ $(this.node).find('#collapseMessages')
+ .unbind('show.bs.collapse')
+ .unbind('shown.bs.collapse')
+ .on('show.bs.collapse', function () {
+ $(debug.messagesBadgeNode).html(""); // remove number from badge
+ }).on('shown.bs.collapse', function () {
+ debug.messagesTextarea.scrollTop = debug.messagesTextarea.scrollHeight; // scroll to bottom (won't work when hidden)
+ });
+
+ $(this.messagesTextarea)
+ .unbind('mouseenter')
+ .unbind('mouseleave')
+ .mouseenter(function () {
+ $(this).addClass("dont-scroll");
+ })
+ .mouseleave(function () {
+ $(this).removeClass("dont-scroll");
+ });
+
+ $(this.clearMessagesButton)
+ .unbind('mouseenter')
+ .unbind('mouseleave')
+ .unbind('click')
+ .mouseenter(function() {
+ $(this).addClass("btn-warning");
+ }).mouseleave(function() {
+ $(this).removeClass("btn-warning");
+ }).click(function() {
+ debug.messagesTextarea.value = "";
+ $(debug.messagesBadgeNode).html("");
+ $(this).prop('disabled', true);
+ $(this).removeClass("btn-warning");
+ $(this).tooltip("hide");
+ return false;
+ });
+
+ $(this.textwrapMessagesButton).unbind('click')
+ .click(function() {
+ if ($(debug.messagesTextarea).attr("wrap") == "on") {
+ $(this).removeClass("active");
+ $(debug.messagesTextarea).attr("wrap", "off");
+ } else {
+ $(this).addClass("active");
+ $(debug.messagesTextarea).attr("wrap", "on");
+ }
+ return false;
+ });
+
+
+ },
+
+ initToolBar: function() {
+ var debug = this;
+
+ this.toolbarNode.innerHTML = '\
+ <div class="btn-group">\
+ <button class="control btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown">\
+ <span class="glyphicon glyphicon-cog"></span>\
+ <span class="caret"></span>\
+ </button>\
+ <ul class="dropdown-menu">\
+ <li><a class="load-from-url" href="#"><i class="glyphicon glyphicon-import"></i> Load Document</a></li>\
+ <li><a class="save-breakpoints" href="#"><i class="glyphicon glyphicon-floppy-save"></i> Save Breakpoints</a></li>\
+ </ul>\
+ </div>\
+ <div class="btn-group">\
+ <button title="Start Execution" class="start btn btn-default btn-xs"><span class="glyphicon glyphicon-play"></span></button>\
+ <div class="btn-group" data-toggle="buttons-checkbox">\
+ <button title="Pause Execution" class="pause btn btn-default btn-xs"><span class="glyphicon glyphicon-pause"></span></button>\
+ </div>\
+ <button title="Step Execution" class="step btn btn-default btn-xs"><span class="glyphicon glyphicon-step-forward"></span></button>\
+ </div>\
+ '
+ this.controlButtonNode = $(this.toolbarNode).children("div.btn-group").children("button.control")[0];
+ this.controlDropdownNode = $(this.toolbarNode).children("div.btn-group").children("ul.dropdown-menu")[0];
+ this.loadFromURLNode = $(this.toolbarNode).find("a.load-from-url")[0];
+ this.saveBreakpointsNode = $(this.toolbarNode).find("a.save-breakpoints")[0];
+ this.startButtonNode = $(this.toolbarNode).children("div.btn-group").children("button.start")[0];
+ this.pauseButtonNode = $(this.toolbarNode).children("div.btn-group").find("button.pause")[0];
+ this.stepButtonNode = $(this.toolbarNode).children("div.btn-group").children("button.step")[0];
+ this.globalDropDownNode = $(this.toolbarNode).find(".dropdown-menu")[0];
+
+ $(this.loadFromURLNode).unbind('click').click(function() {
+ debug.showLoadDialog();
+ return false;
+ });
+
+ $(this.saveBreakpointsNode)
+ .unbind('click').click(function() {
+ debug.recentDocuments[debug.scxmlEditor.scxmlURL].breakpoints = [];
+ for (var key in debug.breakpoints) {
+ debug.recentDocuments[debug.scxmlEditor.scxmlURL].breakpoints.push(debug.breakpoints[key].toWireFormat());
+ }
+ localStorage.recentDocuments = JSON.stringify(debug.recentDocuments);
+ $(debug.controlButtonNode).effect('highlight', {color: '#3AA453'}, 600);
+ return false;
+ })
+ .closest("li").addClass("disabled");
+
+
+ $(this.startButtonNode).prop('disabled', true);
+ $(this.pauseButtonNode).prop('disabled', true);
+ $(this.stepButtonNode).prop('disabled', true);
+
+ },
+
+ showLoadDialog: function() {
+ var debug = this;
+
+ // do we already have a modal div?
+ $("body").children("div#loadFromURLModal").remove();
+
+ // load from URL form - modal window
+ this.loadFromURLModalNode = $('\
+ <div class="modal fade" id="loadFromURLModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">\
+ <div class="modal-dialog">\
+ <form action="" id="loadFromURLModalForm">\
+ <div class="modal-content">\
+ <div class="modal-header">\
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>\
+ <h4 class="modal-title" id="myModalLabel">Load SCXML from URL</h4>\
+ </div>\
+ <div class="modal-body">\
+ <div class="form-group">\
+ <div class="input-group">\
+ <span class="input-group-btn">\
+ <button title="Recent Documents" type="button" class="btn dropdown-toggle" data-toggle="dropdown">\
+ <span class="glyphicon glyphicon-list-alt"></span>\
+ </button>\
+ <ul class="recent-documents dropdown-menu" role="menu" />\
+ </span>\
+ <input id="loadFromURLModalInput" name="scxmlUrl" class="form-control" placeholder="URL of SCXML document">\
+ </div>\
+ </div>\
+ </div>\
+ <div class="modal-footer">\
+ <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>\
+ <button type="submit" class="btn btn-primary">Load</button>\
+ </div>\
+ </div>\
+ </form>\
+ </div>\
+ </div>\
+ ');
+
+ this.recentDocumentsNode = $(this.loadFromURLModalNode).find("ul.recent-documents")[0];
+ $(this.recentDocumentsNode).prev().attr('disabled', true);
+
+ this.recentDocuments = {};
+ if (localStorage.recentDocuments) {
+ this.recentDocuments = JSON.parse(localStorage.recentDocuments);
+ for (var key in this.recentDocuments) {
+ if ((key.substring(0, 4) != "http") && (key.substring(0, 4) != "file")) {
+ // no idea, but it's not a document
+ delete this.recentDocuments[key];
+ continue;
+ }
+ var recentURL = key; // not sure, but key is not available in callback
+ // we found a document, enable "recent" button
+ $(this.recentDocumentsNode).prev().attr('disabled', false);
+
+ var recentDocumentItem = $('\
+ <li><a href="#"><!-- i class="glyphicon glyphicon-floppy-open" style="margin-right: 0.7em" / -->' + key + '<span class="recent-document-features"></span></a></li>\
+ ');
+ var recentDocumentFeatures = $(recentDocumentItem).find("span.recent-document-features");
+ $(recentDocumentItem).children("a").click(function() {
+ $(debug.loadFromURLModalNode).find("input#loadFromURLModalInput").val(recentURL);
+ $(debug.recentDocumentsNode).dropdown("toggle");
+ return false;
+ });
+ if ('breakpoints' in this.recentDocuments[key] && this.recentDocuments[key].breakpoints.length > 0) {
+ $(recentDocumentFeatures).append('<i title="Has Breakpoints" class="glyphicon glyphicon-th-list">');
+ }
+
+ $(this.recentDocumentsNode).append(recentDocumentItem);
+ }
+ }
+
+ $("body").append(this.loadFromURLModalNode);
+
+ var fromURLValidator = $(this.loadFromURLModalNode).find("form#loadFromURLModalForm").validate({ // initialize the plugin
+ rules: { scxmlUrl: { required: true, url2: true }},
+ highlight: function(element) {
+ $(element).closest('.form-group').removeClass('has-success').addClass('has-error');
+ },
+ success: function(element) {
+ element.text('OK!').addClass('valid').closest('.input-group').removeClass('has-error').addClass('has-success');
+ },
+ submitHandler: function (form) {
+ $(debug.loadFromURLModalNode).modal("hide");
+ return true;
+ }
+ });
+
+ $(this.loadFromURLModalNode).on('hide.bs.modal', function(e) {
+ if (!fromURLValidator.valid()) {
+ $(debug.loadFromURLModalNode).find(".form-group").removeClass('has-success').removeClass('has-error');
+ $(debug.loadFromURLModalNode).find("input#loadFromURLModalInput").val("");
+ } else {
+ var scxmlURL = $(debug.loadFromURLModalNode).find("input#loadFromURLModalInput").val();
+ if (!(scxmlURL in debug.recentDocuments)) {
+ debug.recentDocuments[scxmlURL] = {};
+ }
+ localStorage.recentDocuments = JSON.stringify(debug.recentDocuments);
+ debug.scxmlEditor.loadURL(scxmlURL);
+ }
+ fromURLValidator.resetForm();
+ });
+
+ $(this.loadFromURLModalNode).modal();
+
+ },
+
+ initDataModel: function() {
+ var debug = this;
+
+ $(this.clearDataModelLogButton).prop('disabled', true);
+ $(this.viewDataModelLogButton).prop('disabled', true);
+
+ // add functionality to buttons
+ $(this.clearDataModelLogButton).mouseenter(function() {
+ $(this).addClass("btn-warning");
+ }).mouseleave(function() {
+ $(this).removeClass("btn-warning");
+ }).click(function() {
+
+ return false;
+ });
+
+ this.dataModelListingNode = document.createElement('div');
+
+ this.dataModelEditor = ace.edit(this.dataModelListingNode, {});
+ this.dataModelEditor.getSession().setUseWorker(false);
+ this.dataModelListingNode.style.fontSize='10px';
+ this.dataModelEditor.setTheme("ace/theme/tomorrow");
+ this.dataModelEditor.getSession().setMode("ace/mode/javascript");
+ this.dataModelEditor.setReadOnly(true);
+ // this.dataModelEditor.getSession().setUseWrapMode(true);
+ this.dataModelEditor.setShowPrintMargin(false);
+ this.dataModelEditor.renderer.setShowGutter(false);
+ // this.dataModelEditor.getSession().setValue(vkbeautify.json(initialContent));
+ $(this.dataModelListingNode).css("height", ((this.dataModelEditor.session.getLength() + 10) * 1.5) + "em");
+
+ $(this.dataModelNode).append(this.dataModelListingNode);
+ $(this.dataModelNode).append('\
+ <div class="input-group input-group-sm">\
+ <span class="input-group-btn">\
+ <button title="Quick Command" type="button" class="datamodel-suggest btn dropdown-toggle" data-toggle="dropdown">\
+ <span class="glyphicon glyphicon-list-alt"></span>\
+ </button>\
+ <ul class="suggested-commands dropdown-menu" role="menu">\
+ <li><a href="#">_event</a></li>\
+ <li><a href="#">_name</a></li>\
+ <li><a href="#">_sessionid</a></li>\
+ <li><a href="#">_invokers</a></li>\
+ <li><a href="#">_ioprocessors</a></li>\
+ <li><a href="#">_x</a></li>\
+ </ul>\
+ </span>\
+ <input type="text" class="datamodel-eval form-control">\
+ <div class="input-group-btn">\
+ <button title="Evaluate as String" type="button" class="datamodel-eval btn btn-default"><span class="glyphicon glyphicon-play"></span></button>\
+ </div>\
+ </div>\
+ ');
+
+ this.dataModelInputNode = $(this.dataModelNode).find("input.datamodel-eval")[0];
+ this.dataModelInputButton = $(this.dataModelNode).find("button.datamodel-eval")[0];
+ this.dataModelSuggestButton = $(this.dataModelNode).find("button.datamodel-suggest")[0];
+
+ $(this.dataModelSuggestButton).prop('disabled', true);
+
+ $(this.dataModelNode).find("ul.suggested-commands").find("li")
+ .unbind('click').click(function() {
+ var expression = $(this).children("a").text();
+ $(debug.dataModelInputNode).val(expression);
+ debug.evalOnDataModel(expression);
+ $(this).closest("ul").dropdown('toggle');
+ return false;
+ });
+
+ $(this.dataModelInputNode)
+ .prop('disabled', true)
+ .unbind('keypress').keypress(function(e) {
+ if (e.which == 13)
+ debug.evalOnDataModel($(this).val());
+ });
+
+ $(this.dataModelInputButton)
+ .prop('disabled', true)
+ .unbind('click').click(function() {
+ debug.evalOnDataModel($(debug.dataModelInputNode).val());
+ return false;
+ });
+
+ // $(this.dataModelListingNode).resizable({handles: "n, s"});
+ // $(this.dataModelListingNode).resizable({grid: [100000, 1], alsoResize: $(this.node)});
+ // $(this.dataModelListingNode).unbind('resize').on('resize', function() {
+ // debug.dataModelEditor.resize();
+ // });
+ },
+
+ initBreakpoints: function() {
+ var debug = this;
+
+ $(this.deleteAllBreakpointsButton)
+ .unbind('mouseenter')
+ .unbind('mouseleave')
+ .unbind('click');
+ $(this.deleteAllBreakpointsButton).mouseenter(function() {
+ $(this).addClass("btn-danger");
+ }).mouseleave(function() {
+ $(this).removeClass("btn-danger");
+ }).click(function() {
+ return false;
+ });
+
+ $(this.deleteAllBreakpointsButton).confirmation({
+ singleton: true,
+ container: "body",
+ btnOkClass: "btn btn-danger",
+ btnOkLabel: "Delete All",
+ btnOkIcon: "glyphicon glyphicon-trash",
+ onConfirm: function() {
+ while (debug.breakpoints.length > 0) {
+ debug.removeBreakpoint(debug.breakpoints[0]);
+ }
+ }
+ });
+
+ $(this.disableBreakpointsButton).unbind('click');
+ $(this.disableBreakpointsButton).click(function() {
+ $(this).toggleClass("btn-primary");
+ if ($(this).hasClass("btn-primary")) {
+ debug.enableAllBreakpoints();
+ } else {
+ debug.disableAllBreakpoints();
+ }
+ return false;
+ });
+
+ $(this.addNewBreakpointsButton).unbind('click');
+ $(this.addNewBreakpointsButton).click(function() {
+ debug.addBreakpoint();
+ return false;
+ });
+
+ this.breakpointNode.innerHTML = '\
+ <table class="table table-hover">\
+ </table>\
+ ';
+
+ for (var index in this.breakpoints) {
+ $(this.breakpointNode).children("table").append(this.breakpoints[index].getNode());
+ }
+ },
+
+ initCurrEvent: function() {
+ var debug = this;
+
+ $(this.insertEventButton).prop('disabled', true);
+ $(this.editEventButton).prop('disabled', true);
+ $(this.viewEventButton).prop('disabled', true);
+
+ var initialContent = '';
+
+ var editor = ace.edit(this.currEventNode, {});
+ editor.getSession().setUseWorker(false);
+ this.currEventNode.style.fontSize='10px';
+ editor.setTheme("ace/theme/tomorrow");
+ editor.getSession().setMode("ace/mode/javascript");
+ editor.setReadOnly(true);
+ editor.setShowPrintMargin(false);
+ editor.renderer.setShowGutter(false);
+ try {
+ editor.getSession().setValue(vkbeautify.json(initialContent));
+ } catch(e) {
+ editor.getSession().setValue(initialContent);
+ }
+ $(this.currEventNode).css("height", (editor.session.getLength() * 1.5) + "em");
+
+ this.eventViewModal = $('\
+ <div class="modal fade bs-example-modal-lg" id="eventViewModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">\
+ <div class="modal-dialog modal-lg">\
+ <form action="" id="eventViewModalForm">\
+ <div class="modal-content">\
+ <div class="modal-header">\
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>\
+ <h4 class="modal-title" id="myModalLabel">View / Edit Event</h4>\
+ </div>\
+ <div class="modal-body">\
+ <div class="editor"></div>\
+ </div>\
+ <div class="modal-footer">\
+ <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>\
+ <button type="submit" class="btn btn-primary">Apply</button>\
+ </div>\
+ </div>\
+ </form>\
+ </div>\
+ </div>\
+ ');
+
+
+ $(this.eventViewModal).modal({
+ show: false
+ });
+
+ // $(this.eventViewModal).find("div.modal-dialog").resizable();
+ // $(this.eventViewModal).find("div.modal-dialog").draggable();
+
+ $("body").append(this.eventViewModal);
+
+ // add functionality to buttons
+ $(this.viewEventButton).click(function() {
+ $(debug.eventViewModal).find(".editor");
+
+ var modalEditorNode = $(debug.eventViewModal).find(".editor")[0];
+
+ var modalEditor = ace.edit(modalEditorNode, {});
+ modalEditorNode.style.fontSize='10px';
+ // safari has the cursor somewhat off
+// modalEditorNode.style.font='inherit!important';
+ modalEditor.setTheme("ace/theme/tomorrow");
+ modalEditor.getSession().setMode("ace/mode/javascript");
+ modalEditor.setReadOnly(false);
+ modalEditor.setShowPrintMargin(false);
+ modalEditor.renderer.setShowGutter(true);
+ modalEditor.getSession().setValue(vkbeautify.json(initialContent));
+
+ $(modalEditorNode).css("min-height", (editor.session.getLength() * 1.5) + "em");
+ //$(modalEditorNode).css("height", "80%");
+
+ $(debug.eventViewModal).modal("show");
+ return false;
+ });
+
+ },
+
+ log: function(messages) {
+ var self = this;
+
+ $(this.clearMessagesButton).prop('disabled', false);
+
+ if (!self.messagesTextarea) {
+ console.log(messages);
+ return;
+ }
+
+ function logSingleMsg(msg) {
+ // only show badge when when collapsed
+ if (!$(self.messagesNode).parent().hasClass("in")) {
+ var bagdeContent = parseInt($(self.messagesBadgeNode).html()) || 0;
+ $(self.messagesBadgeNode).html(bagdeContent + 1);
+ }
+
+ if (typeof(msg) === 'string') {
+ self.messagesTextarea.value += msg.trim() + "\n";
+ } else if (typeof(msg) === 'object') {
+ if ('formatted' in msg) {
+ self.messagesTextarea.value += msg.formatted.trim() + "\n";
+ } else if('message' in msg) {
+ self.messagesTextarea.value += msg.message.trim() + "\n";
+ } else {
+ try {
+ self.messagesTextarea.value += vkbeautify.json(msg) + "\n";
+ } catch(e) {
+ self.messagesTextarea.value += JSON.stringify(msg).trim() + "\n";
+ }
+ }
+ } else {
+ //not sure what to do here
+ self.messagesTextarea.value += msg;
+ }
+ }
+
+ if (messages instanceof Array) {
+ for (var key in messages) {
+ logSingleMsg(messages[key]);
+ }
+ } else {
+ logSingleMsg(messages);
+ }
+
+ // scroll to bottom
+ if (!$(self.messagesTextarea).hasClass("dont-scroll"))
+ self.messagesTextarea.scrollTop = self.messagesTextarea.scrollHeight;
+ },
+
+ // find a breakpoint by value and return its index
+ findBreakpoint: function(breakpoint) {
+ for (var index in this.breakpoints) {
+ var currBreakpoint = this.breakpoints[index];
+
+ // $("body").append("<pre>" + JSON.stringify(breakpoint.toWireFormat()) + "</pre><br />");
+ // $("body").append("<pre>" + JSON.stringify(currBreakpoint.toWireFormat()) + "</pre><br />");
+ if (Object.identical(breakpoint.toWireFormat(), currBreakpoint.toWireFormat())) {
+ return index;
+ }
+ }
+ return -1;
+ },
+
+ editBreakpoint: function(breakpoint) {
+ breakpoint.showEditDialog()
+ },
+
+ editedBreakpoint: function(breakpoint, oldData) {
+ // override to update your debugger
+ },
+
+ skipToBreakpoint: function(breakpoint) {
+ },
+
+ addBreakpoint: function(breakpoint) {
+ var debug = this;
+ if (breakpoint) {
+ breakpoint.scxmlEditor = this.scxmlEditor;
+ breakpoint.scxmlDebugger = this;
+
+ if (this.findBreakpoint(breakpoint) < 0) {
+ // add as new breakpoint
+ this.breakpoints.push(breakpoint);
+ var breakpointNode = breakpoint.getNode();
+ if (!debug.breakpointsEnabled) {
+ $(breakpointNode).find("button.enable").prop('disabled', true);
+ $(breakpointNode).find("button.skipto").prop('disabled', true);
+ }
+ $(this.breakpointNode).children("table").append(breakpointNode);
+ }
+ } else {
+ // show dialog to add a breakpoint
+ var newBreakpoint = new SCXMLEditor.Debugger.Breakpoint({
+ scxmlEditor: this.scxmlEditor,
+ scxmlDebugger: this
+ });
+ debug.editBreakpoint(newBreakpoint);
+ }
+ },
+
+ removeBreakpoint: function(breakpoint) {
+ var debug = this;
+ var breakPointIndex = this.findBreakpoint(breakpoint);
+ if (breakPointIndex >= 0) {
+ this.breakpoints.splice(breakPointIndex, 1);
+ $(this.breakpointNode).children("table").find("tr").slice(breakPointIndex, breakPointIndex + 1).detach();
+ }
+ },
+
+ highlightBreakpoint: function(breakpoint) {
+ var breakPointIndex = this.findBreakpoint(breakpoint);
+ if (breakPointIndex < 0)
+ return;
+
+ // open breakpoint collapsible
+ $(this.node).find("div.breakpoints").find("div.panel-collapse").addClass("in");
+
+ // highlight breakpoint
+ $(this.breakpointNode)
+ .children("table")
+ .find("tr").slice(breakPointIndex, breakPointIndex + 1)
+ .addClass("info");
+
+ },
+
+ unhighlightAllBreakpoints: function() {
+ $(this.breakpointNode)
+ .children("table")
+ .find("tr").removeClass("info");
+ },
+
+ enableBreakpoint: function(breakpoint) {
+ var breakPointIndex = this.findBreakpoint(breakpoint);
+ if (breakPointIndex < 0)
+ return;
+
+ $(this.breakpointNode)
+ .children("table")
+ .find("tr").slice(breakPointIndex, breakPointIndex + 1)
+ .find("button.enable").addClass('btn-primary');
+
+ $(this.breakpointNode)
+ .children("table")
+ .find("tr").slice(breakPointIndex, breakPointIndex + 1)
+ .find("button.skipto")
+ .prop('disabled', false);
+
+ },
+
+ disableBreakpoint: function(breakpoint) {
+ var breakPointIndex = this.findBreakpoint(breakpoint);
+ if (breakPointIndex < 0)
+ return;
+
+ $(this.breakpointNode)
+ .children("table")
+ .find("tr").slice(breakPointIndex, breakPointIndex + 1)
+ .find("button.enable").removeClass('btn-primary');
+
+ $(this.breakpointNode)
+ .children("table")
+ .find("tr").slice(breakPointIndex, breakPointIndex + 1)
+ .find("button.skipto")
+ .prop('disabled', true);
+ },
+
+ disableAllBreakpoints: function() {
+ this.breakpointsEnabled = false;
+ $(this.breakpointNode).find("button.enable").prop('disabled', true);
+ $(this.breakpointNode).find("button.skipto").prop('disabled', true);
+ },
+
+ enableAllBreakpoints: function() {
+ this.breakpointsEnabled = true;
+
+ $(this.breakpointNode).find("button.enable").each(function() {
+ $(this).prop('disabled', false);
+ if ($(this).hasClass("btn-primary")) {
+ $(this).parent().children("button.skipto").prop('disabled', false);
+ }
+ });
+ },
+
+ evalOnDataModel: function() {
+ },
+
+ attachDebug: function(sessionId) {
+ },
+
+ prepareDebug: function() {
+ this.scxmlText = SCXMLEditor.xmlToString(this.scxmlEditor.scxmlDOM);
+ },
+
+ startDebug: function() {
+ this.unhighlightAllBreakpoints();
+ },
+
+ stopDebug: function() {
+ this.unhighlightAllBreakpoints();
+ },
+
+ stepDebug: function() {
+ this.unhighlightAllBreakpoints();
+ },
+
+ });
+
+
+ SCXMLEditor.Debugger.Breakpoint = Base.extend({
+ constructor: function(params) {
+ $.extend(this, params);
+ this.seqNr = SCXMLEditor.Debugger.Breakpoint.seqNr++;
+ },
+
+ data: {}, // all data describing this breakpoint
+ editNodes: {}, // html nodes of form-groups during editing - needed to retain input - TODO
+
+ breakpointTypes: {
+ "categories": [
+ { id: "state", label: "States" },
+ { id: "event", label: "Events" },
+ { id: "transition", label: "Transitions" },
+ { id: "executable", label: "Executable Content" },
+ { id: "invoke", label: "Invokers" },
+ { id: "configuration", label: "Configuration" },
+ ],
+ "types": [
+ { id: "state-before-enter",
+ label: "Before Entering State", category: "state",
+ hint: "Stop execution <b>before</b> a given state is <b>entered</b>."
+ },
+ { id: "state-after-enter",
+ label: "After Entering State", category: "state",
+ hint: "Stops execution <b>after</b> a given state is <b>entered</b>."
+ },
+ { id: "state-before-exit",
+ label: "Before Exiting State", category: "state",
+ hint: "Stops execution <b>before</b> a given state was <b>exited</b>."
+ },
+ { id: "state-after-exit",
+ label: "After Exiting State", category: "state",
+ hint: "Stops execution <b>after</b> a given state was <b>exited</b>."
+ },
+ { id: "event-before",
+ label: "Before Processing Event", category: "event",
+ hint: "Stops execution <b>before</b> a given event is processed."
+ },
+ { id: "transition-before",
+ label: "Before Taking Transition", category: "transition",
+ hint: "Stops execution <b>before</b> a given transition is taken."
+ },
+ { id: "transition-after",
+ label: "After Taking Transition", category: "transition",
+ hint: "Stops execution <b>after</b> a given transition was taken."
+ },
+ { id: "invoker-before-invoke",
+ label: "Before Starting Invoker", category: "invoke",
+ hint: "Stop execution <b>before</b> an invoker is <b>started</b>."
+ },
+ { id: "invoker-after-invoke",
+ label: "After Starting Invoker", category: "invoke",
+ hint: "Stops execution <b>after</b> an invoker is <b>started</b>."
+ },
+ { id: "invoker-before-cancel",
+ label: "Before Cancelling Invoker", category: "invoke",
+ hint: "Stop execution <b>before</b> an invoker is <b>cancelled</b>."
+ },
+ { id: "invoker-after-cancel",
+ label: "After Cancelling Invoker", category: "invoke",
+ hint: "Stops execution <b>after</b> an invoker is <b>cancelled</b>."
+ },
+ { id: "executable-before",
+ label: "Before Executing Content", category: "executable",
+ hint: "Stops <b>before</b> executing content."
+ },
+ { id: "executable-after",
+ label: "After Executing Content", category: "executable",
+ hint: "Stops <b>after</b> executing content."
+ },
+ { id: "microstep-before",
+ label: "Before Microstep", category: "configuration",
+ hint: "Stops execution <b>before</b> processing a microstep."
+ },
+ { id: "microstep-after",
+ label: "After Microstep", category: "configuration",
+ hint: "Stops execution <b>after</b> after a microstep was processed."
+ },
+ { id: "stable-on",
+ label: "On Stable Configuration", category: "configuration",
+ hint: "Stops execution when the interpreter reached a stable configuration and waits for events."
+ }
+ ]
+ },
+
+ isEnabled: function() {
+ return true;
+ },
+
+ toWireFormat: function() {
+ var jsonData = jQuery.extend(true, {}, this.data);
+
+ if ('breakpointType' in this.data) {
+ var typeComponents = this.data.breakpointType.split("-");
+ jsonData.subject = typeComponents[0];
+ jsonData.when = typeComponents[1];
+ if (typeComponents.length > 2) {
+ jsonData.action = typeComponents[2];
+ } else {
+ delete jsonData.action;
+ }
+ }
+ delete jsonData.breakpointType;
+
+ return jsonData;
+ },
+
+ showEditDialog: function() {
+ var breakpoint = this;
+
+ // do we already have a modal div?
+ $("body").children("div#editBreakpointModal").remove();
+
+ this.editModalNode = $('\
+ <div class="modal fade" id="editBreakpointModal" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">\
+ <div class="modal-dialog">\
+ <form action="" class="form-horizontal" role="form">\
+ <div class="modal-content">\
+ <div class="modal-header">\
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>\
+ <h4 class="modal-title" id="myModalLabel"></h4>\
+ </div>\
+ <div class="modal-body">\
+ <div class="form-group">\
+ <label for="breakpointType" class="col-sm-2 control-label">Type</label>\
+ <div class="col-sm-10 type-select"></div>\
+ </div>\
+ <div class="col-sm-2"></div><div class="col-sm-10 alert alert-info"></div>\
+ </div>\
+ <div class="modal-footer">\
+ <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>\
+ <button type="submit" class="btn btn-primary">Apply</button>\
+ </div>\
+ </div>\
+ </form>\
+ </div>\
+ </div>\
+ ');
+
+ $(this.editModalNode).find("div.modal-body").find("div.type-select").append(this._typeSelectNode());
+ $("body").append(this.editModalNode);
+
+ this.editTypeInfoNode = $(this.editModalNode).find("div.alert-info")[0];
+ this.editTypeNode = $(this.editModalNode).find("div.modal-body")[0];
+
+ var stateNames = this.scxmlEditor.getAllStateIds();
+
+ var editTypeSelectNode = $(this.editTypeNode).find("div.type-select").children("select");
+ if ('breakpointType' in this.data) {
+ $(editTypeSelectNode).val(this.data.breakpointType);
+ $(this.editModalNode).find("h4.modal-title").text("Edit Breakpoint");
+ } else {
+ $(this.editModalNode).find("h4.modal-title").text("Add Breakpoint");
+ }
+
+ $(this.editModalNode).find("form").removeData(); // important as closures persist with dom
+ $(this.editModalNode).find("form").validate({
+ debug: true,
+ highlight: function(element) {
+ $(element).closest('.form-group').removeClass('has-success has-warning').addClass('has-error');
+ },
+ success: function(element) {
+ var state = element.closest('.form-group').find("input[name=stateId], input[name=toStateId], input[name=fromStateId]").val();
+ if (state && $.inArray(state, stateNames) < 0) {
+ element.text('Warning: No such state!').addClass('valid').closest('.form-group').removeClass('has-success has-error').addClass('has-warning');
+ return;
+ }
+ element.text('OK!').addClass('valid').closest('.form-group').removeClass('has-error has-warning').addClass('has-success');
+ },
+ submitHandler: function (form) {
+ $(breakpoint.editModalNode).modal("hide");
+
+ // reset
+ var oldData = jQuery.extend(true, {}, breakpoint.data);
+ breakpoint.data = {};
+
+ // copy over form fields
+ $(breakpoint.editTypeNode).find("textarea[name], input[name], select[name]").each(function() {
+ var name = $(this).attr("name");
+ var value = $(this).val();
+ if (name.length > 0 && value.length > 0) {
+ breakpoint.data[name] = value;
+ }
+ })
+ console.log(breakpoint.data);
+ if (breakpoint.node) {
+ breakpoint._updateLabelNode();
+ breakpoint._updateConditionNode();
+ }
+ if (oldData.length > 0) {
+ breakpoint.scxmlDebugger.editedBreakpoint(breakpoint, oldData);
+ } else {
+ breakpoint.scxmlDebugger.addBreakpoint(breakpoint);
+ }
+ return true;
+ }
+ });
+
+ $(breakpoint.editTypeNode).children(".form-group").not(':first').remove();
+ $(breakpoint.editTypeNode).children("input[type=hidden]").remove(); // hidden fields
+
+
+ $(editTypeSelectNode).change(function () {
+ $(breakpoint.editTypeNode).children(".form-group").not(':first').remove();
+ $(breakpoint.editTypeNode).children("input[type=hidden]").remove(); // hidden fields
+ breakpoint._updateEditTypeDOM($(editTypeSelectNode).val());
+ });
+ this._updateEditTypeDOM($(editTypeSelectNode).val());
+
+ $(this.editModalNode).modal();
+ },
+
+ _updateEditTypeDOM: function(typeId) {
+ var breakpoint = this;
+ var breakpointType = $.grep(this.breakpointTypes["types"], function(e){ return e.id == typeId; })[0];
+ this.editTypeInfoNode.innerHTML = breakpointType.hint;
+
+ // $(this.editTypeNode).append('\
+ // <input type="hidden" name="label" value="' + breakpointType.label + '" />\
+ // ');
+
+ var stateNames = this.scxmlEditor.getAllStateIds();
+
+ switch(true) {
+ case /state-.*/.test(typeId):
+ var editTypeStateFormGroupNode = $('\
+ <div class="form-group">\
+ <label for="stateId" class="col-sm-2 control-label">State</label>\
+ <div class="col-sm-10">\
+ <input class="form-control" type="text" name="stateId" placeholder="The id of the state"/>\
+ </div>\
+ </div>\
+ ');
+ $(this.editTypeNode).append(editTypeStateFormGroupNode);
+ $(editTypeStateFormGroupNode).find("input[name=stateId]")
+ .val('stateId' in breakpoint.data ? breakpoint.data.stateId : "")
+ .typeahead({local: stateNames})
+ .rules('add', 'required');
+
+ break;
+ case /event-.*/.test(typeId):
+ var editTypeEventFormGroupNode = $('\
+ <div class="form-group">\
+ <label for="eventName" class="col-sm-2 control-label">Event</label>\
+ <div class="col-sm-10">\
+ <input class="form-control" type="text" name="eventName" placeholder="The name or prefix of the event"/>\
+ </div>\
+ </div>\
+ ');
+ $(this.editTypeNode).append(editTypeEventFormGroupNode);
+ $(editTypeEventFormGroupNode).find("input[name=eventName]")
+ .val('eventName' in breakpoint.data ? breakpoint.data.eventName : "")
+ .rules('add', 'required');
+ break;
+ case /transition-.*/.test(typeId):
+ var editTypeTransitionFormGroupNode = $('\
+ <div class="form-group">\
+ <label for="transSource" class="col-sm-2 control-label">From State</label>\
+ <div class="col-sm-10">\
+ <input class="form-control" type="text" name="transSource" placeholder="The id of the source state"/>\
+ </div>\
+ </div>\
+ <div class="form-group">\
+ <label for="transTarget" class="col-sm-2 control-label">To State</label>\
+ <div class="col-sm-10">\
+ <input class="form-control" type="text" name="transTarget" placeholder="The id of the destination state"/>\
+ </div>\
+ </div>\
+ ');
+ $(this.editTypeNode).append(editTypeTransitionFormGroupNode);
+ $(editTypeTransitionFormGroupNode).find("input[name=transSource]")
+ .val('transSource' in breakpoint.data ? breakpoint.data.transSource : "")
+ .typeahead({local: stateNames});
+ $(editTypeTransitionFormGroupNode).find("input[name=transTarget]")
+ .val('transTarget' in breakpoint.data ? breakpoint.data.transTarget : "")
+ .typeahead({local: stateNames});
+ break
+ case /invoker-.*/.test(typeId):
+ var editTypeInvokerFormGroupNode = $('\
+ <div class="form-group">\
+ <label for="invokeId" class="col-sm-2 control-label">Invoke ID</label>\
+ <div class="col-sm-10">\
+ <input class="form-control" type="text" name="invokeId" placeholder="The id of the invoker"/>\
+ </div>\
+ </div>\
+ <div class="form-group">\
+ <label for="invokeType" class="col-sm-2 control-label">Invoke Type</label>\
+ <div class="col-sm-10">\
+ <input class="form-control" type="text" name="invokeType" placeholder="The type of the invoker"/>\
+ </div>\
+ </div>\
+ ');
+ $(this.editTypeNode).append(editTypeInvokerFormGroupNode);
+ $(editTypeInvokerFormGroupNode).find("input[name=invokeId]")
+ .val('invokeId' in breakpoint.data ? breakpoint.data.invokeId : "");
+ $(editTypeInvokerFormGroupNode).find("input[name=invokeType]")
+ .val('invokeType' in breakpoint.data ? breakpoint.data.invokeType : "");
+ break
+
+ case /executable-.*/.test(typeId):
+ var editTypeExecutableFormGroupNode = $('\
+ <div class="form-group">\
+ <label for="executableName" class="col-sm-2 control-label">Element Name</label>\
+ <div class="col-sm-10">\
+ <input class="form-control" type="text" name="executableName" placeholder="The executable\'s element name"/>\
+ </div>\
+ </div>\
+ <div class="form-group">\
+ <label for="executableXPath" class="col-sm-2 control-label">Element XPath</label>\
+ <div class="col-sm-10">\
+ <input class="form-control" type="text" name="executableXPath" placeholder="The executable\'s XPath expression"/>\
+ </div>\
+ </div>\
+ ');
+ $(this.editTypeNode).append(editTypeExecutableFormGroupNode);
+ $(editTypeExecutableFormGroupNode).find("input[name=executableName]")
+ .val('executableName' in breakpoint.data ? breakpoint.data.executableName : "");
+ $(editTypeExecutableFormGroupNode).find("input[name=executableXPath]")
+ .val('executableXPath' in breakpoint.data ? breakpoint.data.executableXPath : "");
+ break;
+ default:
+ break;
+ }
+
+ var editTypeConditionFormGroupNode = $('\
+ <div class="form-group">\
+ <label for="condition" class="col-sm-2 control-label">Condition</label>\
+ <div class="col-sm-10">\
+ <textarea class="form-control" rows="4" style="resize:vertical;" name="condition" placeholder="An optional boolean expression evaluated on the datamodel. Breakpoint is only triggered if true." />\
+ </div>\
+ </div>\
+ ');
+ $(editTypeConditionFormGroupNode).find("textarea[name=condition]")
+ .val('condition' in breakpoint.data ? breakpoint.data.condition : "")
+
+ $(this.editTypeNode).append(editTypeConditionFormGroupNode);
+ },
+
+ getNode: function() {
+ var self = this;
+ this.node = $('\
+ <tr>\
+ <td>\
+ <button title="Enable this Breakpoint" class="enable btn btn-default btn-xs btn-primary"><span class="glyphicon glyphicon-ok"></span></button><br />\
+ <button title="Skip to this Breakpoint" class="skipto btn btn-default btn-xs"><span class="glyphicon glyphicon-fast-forward"></span></button>\
+ </td>\
+ <td width="100%">\
+ <span class="breakpoint-label"></span>\
+ <span class="breakpoint-condition"></span>\
+ </td>\
+ <td>\
+ <button title="Edit this Breakpoint" class="edit btn btn-default btn-xs"><span class="glyphicon glyphicon-wrench"></span></button><br />\
+ <button title="Remove this Breakpoint" class="delete btn btn-default btn-xs"><span class="glyphicon glyphicon-trash"></span></button>\
+ </td>\
+ </tr>\
+ ');
+
+ this.labelNode = $(this.node).find("span.breakpoint-label")[0];
+ this.conditionNode = $(this.node).find("span.breakpoint-condition")[0];
+ this.enableButtonNode = $(this.node).find("button.enable")[0];
+ this.skipToButtonNode = $(this.node).find("button.skipto")[0];
+ this.deleteButtonNode = $(this.node).find("button.delete")[0];
+ this.editButtonNode = $(this.node).find("button.edit")[0];
+
+ $(this.enableButtonNode)
+ .unbind('click')
+ .click(function() {
+ if ($(this).hasClass("btn-primary")) {
+ self.scxmlDebugger.disableBreakpoint(self);
+ } else {
+ self.scxmlDebugger.enableBreakpoint(self);
+ }
+ return false;
+ });
+
+ $(this.deleteButtonNode)
+ .unbind('mouseenter')
+ .unbind('mouseleave')
+ .unbind('click')
+ .mouseenter(function() {
+ $(this).addClass("btn-danger");
+ })
+ .mouseleave(function() {
+ $(this).removeClass("btn-danger");
+ })
+ .click(function() {
+ self.scxmlDebugger.removeBreakpoint(self);
+ return false;
+ });
+
+ $(this.editButtonNode)
+ .unbind('click').click(function() {
+ self.showEditDialog()
+ return false;
+ });
+
+ $(this.skipToButtonNode)
+ .unbind('click').click(function() {
+ self.scxmlDebugger.skipToBreakpoint(self);
+ return false;
+ });
+
+ this._updateLabelNode();
+ this._updateConditionNode();
+
+ return this.node;
+ },
+
+ toDescription: function() {
+ var wireFormat = this.toWireFormat();
+
+ var breakpointString = '';
+ breakpointString += ('when' in wireFormat ? wireFormat.when + ' ' : '');
+ breakpointString += ('action' in wireFormat ? wireFormat.action + ' ' : '');
+ breakpointString += ('subject' in wireFormat ? wireFormat.subject + ' ' : '');
+
+ breakpointString += (wireFormat.subject === 'state' ? wireFormat.stateId + ' ' : '');
+ breakpointString += (wireFormat.subject === 'event' ? wireFormat.eventName + ' ' : '');
+ if (wireFormat.subject === 'transition') {
+ breakpointString += ('transSource' in wireFormat ? wireFormat.transSource + ' ' : '* ');
+ breakpointString += '&rArr; ';
+ breakpointString += ('transTarget' in wireFormat ? wireFormat.transTarget + ' ' : '* ');
+ }
+
+ return breakpointString;
+ },
+
+ _updateLabelNode: function() {
+ this.labelNode.innerHTML = 'Halt ' + this.toDescription();
+ },
+
+ _updateConditionNode: function() {
+ if (this.data.condition) {
+ this.conditionNode.innerHTML = "<br/>But only when <code>" + this.data.condition + "</code>";
+ } else {
+ this.conditionNode.innerHTML = '';
+ }
+ },
+
+ _typeSelectNode: function() {
+ // establish optgroups for selecting the breakpoint type
+ var optGroups = {};
+ for (var index in this.breakpointTypes.types) {
+ var bp = this.breakpointTypes.types[index];
+ var optGroup = bp.category;
+ if (!(optGroup in optGroups)) optGroups[optGroup] = [];
+ optGroups[optGroup].push(bp);
+ }
+
+ var breakpointTypeSelectNode = document.createElement('select');
+ breakpointTypeSelectNode.className = 'form-control';
+ $(breakpointTypeSelectNode).attr("name", "breakpointType");
+
+ for (var optGroup in this.breakpointTypes.categories) {
+ var cat = this.breakpointTypes.categories[optGroup];
+ var optGroupNode = document.createElement('optgroup');
+ $(optGroupNode).attr('label', cat.label);
+
+ for(var type in optGroups[cat.id]) {
+ var breakpointType = optGroups[cat.id][type];
+ var optionNode = document.createElement('option');
+ $(optionNode).attr('value', breakpointType.id);
+ optionNode.innerHTML = breakpointType.label;
+ $(optionNode).appendTo(optGroupNode);
+ }
+ $(optGroupNode).appendTo(breakpointTypeSelectNode);
+ }
+
+ return breakpointTypeSelectNode;
+ },
+
+ });
+ SCXMLEditor.Debugger.Breakpoint.seqNr = 0;
+
+ SCXMLEditor.Debugger.USCXML = SCXMLEditor.Debugger.extend({
+ constructor: function(params) {
+ this.base(params);
+
+ this.isConnected = false; // we have a connection to the uscxml server
+ this.isPrepared = false; // we uploaded a document
+ this.isRunning = false; // the interpreter on the server is running
+ this.isPaused = false; // the interpreter is running but paused
+ this.hasDOMLoaded = false; // the client has a DOM loaded
+ this.isAttached = false; // we are attached to a server session
+ this.isPolling = false; // we have a unreplied poll request standing
+ this.sessionId = "";
+ },
+
+ _updateDebugControls: function() {
+ // all buttons are disabled per base class
+ $(this.startButtonNode).prop('disabled', true)
+ $(this.pauseButtonNode).prop('disabled', true);
+ $(this.stepButtonNode).prop('disabled', true);
+
+ var status = "";
+ status += (this.isConnected ? "CON " : "con ");
+ status += (this.isPolling ? "POL " : "pol ");
+ status += (this.isPrepared ? "PRP " : "prp ");
+ status += (this.isRunning ? "RUN " : "run ");
+ status += (this.isPaused ? "PAU " : "pau ");
+ status += (this.hasDOMLoaded ? "DOM " : "dom ");
+ status += (this.isAttached ? "ATC " : "atc ");
+
+ $(this.statusIndicatorNode).html(status);
+
+ // some sanity
+ if (!this.isConnected) {
+ $("body").find(".dom").text("");
+ $(this.currentBreakpointNode).html("");
+ this.isPaused = false;
+ this.isRunning = false;
+ this.isAttached = false;
+ this.isPrepared = false;
+ }
+
+ if (!this.isRunning) {
+ this.isPaused = false;
+ $(this.currentBreakpointNode).html("");
+ }
+
+ // toggle button icons
+ if (this.isRunning) { // switch play and stop
+ $(this.startButtonNode).children("span.glyphicon").removeClass("glyphicon-play").addClass("glyphicon-stop");
+ $(this.startButtonNode).prop('title', 'Stop Execution');
+ } else {
+ $(this.startButtonNode).children("span.glyphicon").removeClass("glyphicon-stop").addClass("glyphicon-play");
+ $(this.startButtonNode).prop('title', 'Start Execution');
+ }
+
+ if (!this.isPaused) { // switch pause and resume
+ $(this.pauseButtonNode).removeClass("active");
+ $(this.pauseButtonNode).prop('title', 'Pause Execution');
+ this.unhighlightAllBreakpoints();
+ $(this.currentBreakpointNode).html("");
+ } else {
+ $(this.pauseButtonNode).addClass("active");
+ $(this.pauseButtonNode).prop('title', 'Resume Execution');
+ }
+
+ if (this.isConnected && (this.hasDOMLoaded || this.isAttached)) {
+ // are we connected, not running and have a DOM or are attached?
+ if (!this.isAttached) {
+ $(this.startButtonNode).prop('disabled', false);
+ }
+ if (this.isRunning) {
+ $(this.pauseButtonNode).prop('disabled', false);
+ $(this.stepButtonNode).prop('disabled', false);
+ } else {
+ $(this.startButtonNode).prop('disabled', false)
+ $(this.pauseButtonNode).prop('disabled', true);
+ $(this.stepButtonNode).prop('disabled', false);
+ }
+ return;
+ } else {
+ $("body").find(".dom").text("");
+ }
+ },
+
+ loadDOM: function(scxmlDOM) {
+ this.hasDOMLoaded = true;
+ this._updateDebugControls();
+ },
+
+ getNode: function() {
+ this.base();
+ var debug = this;
+
+ // a new menu item
+ $(this.controlDropdownNode).append('\
+ <li role="presentation" class="divider"></li>\
+ <li class="disabled"><a href="#"><i class="glyphicon glyphicon-resize-small"></i> Attach to Instance</a></li>\
+ ');
+
+ // the connect bar
+ $(this.node).find(".debug-header").after('\
+ <div class="panel-body input-group input-group-sm">\
+ <input type="text" class="form-control connect" placeholder="uSCMXL Interpreter URL">\
+ <span class="input-group-btn">\
+ <button title="Connect" class="btn btn-default ladda-button connect" data-spinner-color="#000" data-style="zoom-in">\
+ <span class="ladda-label"><span class="ui-icon ui-icon-unlocked"></span></span></button>\
+ </span>\
+ </div>\
+ ');
+
+ this.connectInputNode = $(this.node).find("input.connect");
+ this.connectButtonNode = $(this.connectInputNode).next().children("button.connect")[0];
+
+ if (this.debuggerURL) {
+ $(this.connectInputNode).val(this.debuggerURL);
+ } else {
+ $(this.connectInputNode).attr("placeholder", "URL of uscxml-debug");
+ }
+
+ $(this.connectInputNode).focus(function () {
+ $(debug.connectButtonNode)
+ .removeClass("btn-warning")
+ .find("span.ui-icon")
+ .removeClass("ui-icon-alert ui-icon-locked")
+ .addClass("ui-icon-unlocked");
+ });
+
+ $(this.connectInputNode).unbind('keypress').keypress(function(e) {
+ if (e.which == 13)
+ $(debug.connectButtonNode).trigger('click');
+ });
+
+
+ $(this.connectButtonNode).unbind("click");
+ $(this.connectButtonNode).click(function() {
+ debug.connectSpinner = Ladda.create(this);
+ debug.connectSpinner.start();
+ if (debug.isConnected) {
+ debug.disconnect();
+ } else {
+ debug.connect($(debug.connectInputNode).val());
+ }
+ });
+
+ // debug controls
+ $(this.startButtonNode)
+ .unbind('click').click(function () {
+ $(this).tooltip("hide");
+ if ($(this).children(".glyphicon").hasClass("glyphicon-play")) {
+ debug.startDebug();
+ } else {
+ debug.stopDebug();
+ }
+ });
+
+ $(this.pauseButtonNode)
+ .unbind('click').click(function() {
+ $(this).tooltip("hide");
+ if (debug.isPaused) {
+ debug.resumeDebug();
+ } else {
+ debug.pauseDebug();
+ }
+ });
+
+ $(this.stepButtonNode)
+ .unbind('click').click(function() {
+ $(this).tooltip("hide");
+ debug.stepDebug();
+ });
+
+ this._updateDebugControls();
+
+ return this.node;
+ },
+
+ _indicateLockedConnection: function() {
+ $(this.connectButtonNode)
+ .removeClass("btn-warning")
+ .find("span.ui-icon")
+ .removeClass("ui-icon-alert ui-icon-unlocked")
+ .addClass("ui-icon-locked");
+ $(this.connectInputNode).attr('disabled', true);
+ $(this.dataModelInputNode).prop('disabled', false);
+ $(this.dataModelInputButton).prop('disabled', false);
+ $(this.dataModelSuggestButton).prop('disabled', false);
+ },
+
+ _indicateFailedConnection: function() {
+ $(this.connectButtonNode)
+ .addClass("btn-warning")
+ .find("span.ui-icon")
+ .removeClass("ui-icon-locked ui-icon-unlocked")
+ .addClass("ui-icon-alert");
+ },
+
+ _indicateUnlockedConnection: function() {
+ $(this.connectButtonNode)
+ .removeClass("btn-warning")
+ .find("span.ui-icon")
+ .removeClass("ui-icon-locked ui-icon-alert")
+ .addClass("ui-icon-unlocked");
+ $(this.connectInputNode).attr('disabled', false);
+ $(this.dataModelInputNode).prop('disabled', true);
+ $(this.dataModelInputButton).prop('disabled', true);
+ $(this.dataModelSuggestButton).prop('disabled', true);
+ },
+
+ connect: function(debuggerURL) {
+ if (!debuggerURL || debuggerURL.length == 0)
+ return;
+
+ this.debuggerURL = debuggerURL;
+
+ var self = this;
+
+ this.requestWithJSON('/debug/connect', {
+ onFailure: function(dataObj, statusString, xhrObj) {
+ self.isConnected = false;
+ self._indicateFailedConnection();
+ },
+ onSuccess: function(dataObj, statusString, xhrObj) {
+ self.isConnected = true;
+ self._indicateLockedConnection();
+
+ self.log("Client successfully connected to " + self.debuggerURL);
+ self.sessionId = dataObj.session;
+ // send all breakpoints when connected
+ if (self.breakpoints.length > 0) {
+ for (var index in self.breakpoints) {
+ self.addBreakpoint(self.breakpoints[index]);
+ }
+ }
+ // register disconnect handler
+ $(window).unload(function() {
+ self.disconnect();
+ });
+ // update list of sessions from server
+ self.updateServerSessions();
+ // and start long-poller
+ self.pollServer();
+ },
+ onComplete: function(dataObj, statusString) {
+ self.connectSpinner.stop();
+ }
+ });
+ },
+
+ disconnect: function() {
+ if (!this.isConnected)
+ return;
+ var self = this;
+
+ this.requestWithJSON('/debug/disconnect', {
+ async: false,
+ onComplete: function(dataObj, statusString) {
+ self.isConnected = false;
+ self.isPrepared = false;
+ self._indicateUnlockedConnection();
+ self.connectSpinner.stop();
+
+ // remove all previous sessions
+ $(self.controlDropdownNode).children("li.server-session").remove();
+ self.debuggerURL = '';
+ }
+ });
+ },
+
+ updateServerSessions: function() {
+ this.base();
+ if (!this.isConnected)
+ return;
+
+ var self = this;
+
+ this.requestWithJSON('/debug/sessions', {
+ onSuccess: function(dataObj, statusString, xhrObj) {
+ $(self.controlButtonNode).effect('highlight', {color: '#4489CA'}, 600);
+
+ // remove all previous sessions
+ $(self.controlDropdownNode).children("li.server-session").remove();
+ for (var key in dataObj.sessions) {
+ $(self.controlDropdownNode).append('\
+ <li class="server-session" \
+ session-url="' + dataObj.sessions[key].source + '"\
+ session-name="' + dataObj.sessions[key].name + '"\
+ session-id="' + dataObj.sessions[key].id + '"\
+ ><a href="#"><i class="glyphicon glyphicon-link"></i> ' + dataObj.sessions[key].source + '</a></li>\
+ ');
+ }
+ $(self.controlDropdownNode).find("li").unbind('click').click(function() {
+ var session = {
+ sessionId: $(this).attr("session-id"),
+ sessionName: $(this).attr("session-name"),
+ sessionURL: $(this).attr("session-url"),
+ };
+ self.attachDebug(session);
+
+ $(self.controlDropdownNode).dropdown('toggle');
+ return false;
+ });
+ }
+ });
+ },
+
+
+ addBreakpoint: function(breakpoint) {
+ this.base(breakpoint);
+ if (!this.isConnected)
+ return;
+
+ if (!breakpoint) // it's perfectly fine to call this without a breakpoint - will add a new one
+ return;
+
+ this.requestWithJSON('/debug/breakpoint/add', {
+ data: breakpoint.toWireFormat()
+ });
+ },
+
+ removeBreakpoint: function(breakpoint) {
+ this.base(breakpoint);
+ if (!this.isConnected)
+ return;
+ this.requestWithJSON('/debug/breakpoint/remove', {
+ data: breakpoint.toWireFormat()
+ });
+ },
+
+ editedBreakpoint: function(breakpoint, oldData) {
+ this.base(breakpoint, oldData);
+ if (!this.isConnected)
+ return;
+
+ var oldBreakpoint = new SCXMLEditor.Debugger.Breakpoint({ data : oldData });
+ this.requestWithJSON('/debug/breakpoint/remove', {
+ data: oldBreakpoint.toWireFormat()
+ });
+ this.requestWithJSONBlocking('/debug/breakpoint/add', {
+ data: breakpoint.toWireFormat()
+ });
+ },
+
+ skipToBreakpoint: function(breakpoint) {
+ this.base(breakpoint);
+ if (!this.isConnected)
+ return;
+
+ this.requestWithJSON('/debug/breakpoint/skipto', {
+ data: breakpoint.toWireFormat()
+ });
+
+ },
+
+ enableBreakpoint: function(breakpoint) {
+ this.base(breakpoint);
+ if (!this.isConnected)
+ return;
+
+ this.requestWithJSON('/debug/breakpoint/enable', {
+ data: breakpoint.toWireFormat()
+ });
+ },
+
+ disableBreakpoint: function(breakpoint) {
+ this.base(breakpoint);
+ if (!this.isConnected)
+ return;
+
+ this.requestWithJSON('/debug/breakpoint/disable', {
+ data: breakpoint.toWireFormat()
+ });
+ },
+
+ enableAllBreakpoints: function() {
+ this.base();
+ if (!this.isConnected)
+ return;
+
+ this.requestWithJSON('/debug/breakpoint/enable/all');
+ },
+
+ disableAllBreakpoints: function() {
+ this.base();
+ if (!this.isConnected)
+ return;
+
+ this.requestWithJSON('/debug/breakpoint/disable/all');
+
+ },
+
+
+ attachDebug: function(session) {
+ this.base();
+ if (!this.isConnected)
+ return;
+
+ var self = this;
+
+ var data = {
+ attach: session.sessionId,
+ };
+
+ this.requestWithJSON('/debug/attach', {
+ data: data,
+ onSuccess: function(dataObj, statusString, xhrObj) {
+ self.isPrepared = true;
+ self.isRunning = true;
+ self.isAttached = true;
+
+ if (self.scxmlEditor.xmlView) {
+ self.scxmlEditor.xmlView.setXML(dataObj.xml)
+ self.scxmlEditor.xmlView.prettyPrint();
+ }
+
+ // var domText = vkbeautify.xml(dataObj.xml);
+ // $("body").find("pre.dom").text(domText);
+ self.scxmlEditor.setDocumentNameFromURL(session.sessionURL);
+ self._updateDebugControls();
+ }
+ });
+
+ },
+
+ prepareDebug: function() {
+ this.base();
+ if (!this.isConnected)
+ return;
+
+ if (this.isAttached)
+ return;
+
+ var self = this;
+
+ var data = {
+ url: this.scxmlEditor.scxmlURL,
+ xml: this.scxmlText
+ };
+
+ this.requestWithJSON('/debug/prepare', {
+ data: data,
+ onSuccess: function(dataObj, statusString, xhrObj) {
+ self.isPrepared = true;
+ }
+ });
+ },
+
+ startDebug: function() {
+ this.base();
+ if (!this.isConnected)
+ return;
+
+ if (!this.isPrepared)
+ this.prepareDebug();
+
+ var self = this;
+
+ this.requestWithJSON('/debug/start', {
+ onSuccess: function(dataObj, statusString, xhrObj) {
+ self.isRunning = true;
+ }
+ });
+ },
+
+ stopDebug: function() {
+ this.base();
+ if (!this.isConnected)
+ return;
+
+ var self = this;
+
+ this.requestWithJSON('/debug/stop', {
+ onSuccess: function(dataObj, statusString, xhrObj) {
+ self.isRunning = false;
+ self.isPrepared = false;
+ }
+ });
+ },
+
+ pauseDebug: function() {
+ this.base();
+ if (!this.isConnected)
+ return;
+
+ var self = this;
+
+ this.requestWithJSON('/debug/pause', {
+ onSuccess: function(dataObj, statusString, xhrObj) {
+ self.isPaused = true;
+ self._updateDebugControls();
+ }
+ });
+ },
+
+ resumeDebug: function() {
+ this.base();
+ if (!this.isConnected)
+ return;
+
+ var self = this;
+
+ this.requestWithJSON('/debug/resume', {
+ onSuccess: function(dataObj, statusString, xhrObj) {
+ self.isPaused = false;
+ self._updateDebugControls();
+ }
+ });
+ },
+
+ stepDebug: function() {
+ this.base();
+ if (!this.isConnected)
+ return;
+
+ if (!this.isPrepared)
+ this.prepareDebug();
+
+ var self = this;
+
+ this.requestWithJSON('/debug/step', {
+ onSuccess: function(dataObj, statusString, xhrObj) {
+ self.isRunning = true;
+ self.isPaused = true;
+ self._updateDebugControls();
+ }
+ });
+ },
+
+ evalOnDataModel: function(expression) {
+ this.base();
+ if (!this.isConnected)
+ return;
+
+ var self = this;
+
+ var data = {};
+ data.expression = expression;
+
+ this.requestWithJSON('/debug/eval', {
+ data: data,
+ onSuccess: function(dataObj, statusString, xhrObj) {
+ var content = JSON.stringify(dataObj.eval);
+ self.dataModelEditor.getSession().setValue(vkbeautify.json(content));
+
+ },
+ });
+
+ },
+
+ // server will return 'status' with JSON response, dispatch accordingly
+ requestWithJSON: function(url, params) {
+ var self = this;
+ if (!params)
+ params = {};
+
+ params.method = ('method' in params ? params.method : "POST");
+ params.async = ('async' in params ? params.async : true);
+ params.onSuccess = ('onSuccess' in params ? params.onSuccess : function() {});
+ params.onFailure = ('onFailure' in params ? params.onFailure : function(dataObj) { self.log(dataObj); });
+ params.onComplete = ('onComplete' in params ? params.onComplete : function() {});
+ params.onError = ('onError' in params ? params.onError : function() {});
+ params.returnType = ('returnType' in params ? params.returnType : "json");
+ params.sendType = ('sendType' in params ? params.sendType : "application/json; charset=utf-8");
+ params.data = ('data' in params ? params.data : {});
+
+ if (params.sendType.match("^application/json")) {
+ if (this.sessionId.length > 0) // automatically add session
+ params.data.session = this.sessionId;
+ params.data = JSON.stringify(params.data);
+ }
+
+ $.ajax({
+ type: params.method,
+ dataType: params.returnType,
+ async: params.async,
+ data: params.data,
+ contentType: params.sendType,
+ url: this.debuggerURL + url,
+ success: function (dataObj, statusString, xhrObj) {
+ if ("status" in dataObj && dataObj.status === 'success') {
+ params.onSuccess(dataObj, statusString, xhrObj);
+ } else {
+ params.onFailure(dataObj, statusString, xhrObj);
+ }
+ },
+ error: function(xhrObj, statusString, errorString) {
+ self.log("Error from " + self.debuggerURL + url);
+ self.log(statusString + "\n\t" + errorString);
+ self.log(xhrObj);
+
+ self.isConnected = false;
+ self._indicateFailedConnection();
+ params.onError(errorString, statusString, xhrObj);
+ },
+ complete: function(xhrObj, statusString) {
+ params.onComplete(xhrObj, statusString);
+ self._updateDebugControls();
+ }
+ });
+ },
+
+ handleBreakpointReturn: function(serverReturn) {
+ var updateViewer = false;
+ if ('qualified' in serverReturn) {
+ var qualifiedBreakpoint = new SCXMLEditor.Debugger.Breakpoint({ data : serverReturn.qualified });
+ var breakpointString = 'Halted ' + qualifiedBreakpoint.toDescription();
+ $(this.currentBreakpointNode).html(breakpointString);
+
+ if ('xpath' in serverReturn.qualified) {
+ if (this.scxmlEditor.xmlView) {
+ this.scxmlEditor.xmlView.setElementXPath(serverReturn.qualified.xpath);
+ }
+ } else {
+ this.scxmlEditor.xmlView.setElementXPath("");
+ }
+ }
+
+ if ('basicStates' in serverReturn) {
+ if (this.scxmlEditor.xmlView) {
+ this.scxmlEditor.xmlView.setActiveStates(serverReturn['basicStates']);
+ }
+ }
+
+ if (this.scxmlEditor.xmlView) {
+ this.scxmlEditor.xmlView.prettyPrint();
+ }
+
+ if ('breakpoint' in serverReturn) {
+ var breakpoint = new SCXMLEditor.Debugger.Breakpoint({ data : serverReturn.breakpoint });
+ this.highlightBreakpoint(breakpoint);
+ }
+
+ },
+
+ pollServer: function() {
+ this.isPolling = false;
+ this._updateDebugControls();
+
+ if (!this.isConnected)
+ return;
+
+ if (this.sessionId.length == 0)
+ return;
+
+ var self = this;
+ this.isPolling = true;
+ self._updateDebugControls();
+
+ $.ajax({
+ type: "POST",
+ dataType: "json",
+ async: true,
+ timeout: 100000000,
+ data: JSON.stringify({session: this.sessionId}),
+ contentType: "application/json",
+ url: this.debuggerURL + '/debug/poll',
+ success: function (dataObj, statusString, xhrObj) {
+ try {
+ if ('status' in dataObj) {
+ if (dataObj.status !== 'success') {
+ self.log("Error from " + self.debuggerURL + "/debug/poll");
+ self.log(dataObj);
+ return;
+ }
+ }
+ if ('replyType' in dataObj) {
+ //self.log("Received " + dataObj.replyType);
+ if (dataObj.replyType === "log") {
+ self.log(dataObj);
+ } else if (dataObj.replyType === "finished") {
+ self.isRunning = false;
+ self.isPrepared = false;
+ } else if (dataObj.replyType === "breakpoint") {
+ self.isPaused = true;
+ self.handleBreakpointReturn(dataObj);
+ } else {
+ self.log("Unhandled reply from /pollServer");
+ self.log(dataObj);
+ }
+ } else {
+ self.log("Untyped reply from /pollServer");
+ self.log(dataObj);
+ }
+ } catch(e) {
+ self.log("Exception when dispatching server push:");
+ self.log(e);
+ }
+ // dispatch server push here
+ self.pollServer();
+ },
+ error: function(xhrObj, statusString, errorString) {
+ if (xhrObj.statusText === "timeout") {
+ self.pollServer(); // just repoll
+ return;
+ }
+
+ self.log("Error from /pollServer");
+ self.log(statusString + "\n\t" + errorString);
+ self.log(xhrObj);
+
+ self.isConnected = false;
+ self._indicateFailedConnection();
+ },
+ complete: function(xhrObj, statusString) {
+ self._updateDebugControls();
+ }
+ });
+
+ },
+
+ });
+
+ // see http://nealvs.wordpress.com/2010/07/12/how-to-compare-two-javascript-objects-for-equality/
+ function deepEquals(x, y) {
+ for (var p in y) {
+ if (typeof(y[p]) !== typeof(x[p])) return false;
+ if ((y[p] === null) !== (x[p] === null)) return false;
+ switch (typeof(y[p])) {
+ case 'undefined':
+ if (typeof(x[p]) != 'undefined') return false;
+ break;
+ case 'object':
+ if (y[p] !== null && x[p] !== null && (y[p].constructor.toString() !== x[p].constructor.toString() || !y[p].equals(x[p]))) return false;
+ break;
+ case 'function':
+ if (p != 'equals' && y[p].toString() != x[p].toString()) return false;
+ break;
+ default:
+ if (y[p] !== x[p]) return false;
+ }
+ }
+ return true;
+ }
+
+ $(document).ready(function() {
+
+
+ // see http://stackoverflow.com/questions/18754020/bootstrap-3-with-jquery-validation-plugin
+ $.validator.setDefaults({
+ highlight: function(element) {
+ $(element).closest('.form-group').addClass('has-error');
+ },
+ unhighlight: function(element) {
+ $(element).closest('.form-group').removeClass('has-error');
+ },
+ errorElement: 'span',
+ errorClass: 'help-block',
+ errorPlacement: function(error, element) {
+ if(element.parent('.input-group').length) {
+ error.insertAfter(element.parent());
+ } else {
+ error.insertAfter(element);
+ }
+ }
+ });
+
+ var scxmlEdit = new SCXMLEditor({
+ containerNode: $("#scxml-editor")[0]
+ });
+
+ });
+
+ </script>
+</head>
+<body>
+ <div id="scxml-editor"></div>
+ <div class="messages"></div>
+ <div class="dom"></pre>
+</body>
+</html>
diff --git a/src/uscxml/DOMUtils.cpp b/src/uscxml/DOMUtils.cpp
index c38b087..3cf986b 100644
--- a/src/uscxml/DOMUtils.cpp
+++ b/src/uscxml/DOMUtils.cpp
@@ -25,9 +25,14 @@
namespace uscxml {
-std::string DOMUtils::xPathForNode(const Arabica::DOM::Node<std::string>& node) {
+std::string DOMUtils::xPathForNode(const Arabica::DOM::Node<std::string>& node, const std::string& ns) {
std::string xPath;
-
+ std::string nsPrefix;
+
+ if (ns.size() > 0) {
+ nsPrefix = ns + ":";
+ }
+
if (!node || node.getNodeType() != Arabica::DOM::Node_base::ELEMENT_NODE)
return xPath;
@@ -37,12 +42,16 @@ std::string DOMUtils::xPathForNode(const Arabica::DOM::Node<std::string>& node)
case Arabica::DOM::Node_base::ELEMENT_NODE: {
if (HAS_ATTR(curr, "id")) {
// we assume ids to be unique and return immediately
- xPath.insert(0, "//" + TAGNAME(curr) + "[@id=\"" + ATTR(curr, "id") + "\"]");
+ if (ns == "*") {
+ xPath.insert(0, "//*[local-name() = \"" + TAGNAME(curr) + "\"][@id=\"" + ATTR(curr, "id") + "\"]");
+ } else {
+ xPath.insert(0, "//" + nsPrefix + TAGNAME(curr) + "[@id=\"" + ATTR(curr, "id") + "\"]");
+ }
return xPath;
} else {
// check previous siblings to count our index
Arabica::DOM::Node<std::string> sibling = curr.getPreviousSibling();
- int index = 1;
+ int index = 1; // xpath indices start at 1
while(sibling) {
if (sibling.getNodeType() == Arabica::DOM::Node_base::ELEMENT_NODE) {
if (iequals(TAGNAME(sibling), TAGNAME(curr))) {
@@ -51,7 +60,11 @@ std::string DOMUtils::xPathForNode(const Arabica::DOM::Node<std::string>& node)
}
sibling = sibling.getPreviousSibling();
}
- xPath.insert(0, "/" + TAGNAME(curr) + "[" + toStr(index) + "]");
+ if (ns == "*") {
+ xPath.insert(0, "/*[local-name() = \"" + TAGNAME(curr) + "\"][" + toStr(index) + "]");
+ } else {
+ xPath.insert(0, "/" + nsPrefix + TAGNAME(curr) + "[" + toStr(index) + "]");
+ }
}
break;
}
diff --git a/src/uscxml/DOMUtils.h b/src/uscxml/DOMUtils.h
index 731b332..596bb36 100644
--- a/src/uscxml/DOMUtils.h
+++ b/src/uscxml/DOMUtils.h
@@ -35,7 +35,7 @@ namespace uscxml {
class USCXML_API DOMUtils {
public:
- static std::string xPathForNode(const Arabica::DOM::Node<std::string>& node);
+ static std::string xPathForNode(const Arabica::DOM::Node<std::string>& node, const std::string& ns = "");
static std::list<Arabica::DOM::Node<std::string> > getElementsByType(const Arabica::DOM::Node<std::string>& root, Arabica::DOM::Node_base::Type type);
};
diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp
index 2e867ba..bfee23a 100644
--- a/src/uscxml/Interpreter.cpp
+++ b/src/uscxml/Interpreter.cpp
@@ -98,7 +98,7 @@ void InterpreterOptions::printUsageAndExit(const char* progName) {
printf("\n");
printf("Options\n");
printf("\t-v : be verbose\n");
- printf("\t-d : write each configuration as a dot file\n");
+ printf("\t-d : enable debugging via HTTP\n");
printf("\t-lN : Set loglevel to N\n");
printf("\t-tN : port for HTTP server\n");
printf("\t-sN : port for HTTPS server\n");
@@ -120,7 +120,7 @@ InterpreterOptions InterpreterOptions::fromCmdLine(int argc, char** argv) {
optind = 0;
struct option longOptions[] = {
{"verbose", no_argument, 0, 'v'},
- {"dot", no_argument, 0, 'd'},
+ {"debug", no_argument, 0, 'd'},
{"port", required_argument, 0, 't'},
{"ssl-port", required_argument, 0, 's'},
{"ws-port", required_argument, 0, 'w'},
@@ -134,11 +134,6 @@ InterpreterOptions InterpreterOptions::fromCmdLine(int argc, char** argv) {
};
opterr = 0;
- if (argc < 2) {
- options.error = "No SCXML document to evaluate";
- return options;
- }
-
InterpreterOptions* currOptions = &options;
// parse global options
@@ -183,7 +178,7 @@ InterpreterOptions InterpreterOptions::fromCmdLine(int argc, char** argv) {
currOptions->pluginPath = optarg;
break;
case 'd':
- currOptions->useDot = true;
+ currOptions->withDebugger = true;
break;
case 'c':
currOptions->certificate = optarg;
@@ -228,7 +223,7 @@ InterpreterOptions InterpreterOptions::fromCmdLine(int argc, char** argv) {
DONE_PARSING_CMD:
- if (options.interpreters.size() == 0)
+ if (options.interpreters.size() == 0 && !options.withDebugger)
options.error = "No SCXML document to evaluate";
return options;
@@ -328,6 +323,7 @@ Interpreter Interpreter::fromURI(const std::string& uri) {
// try to establish URI root for relative src attributes in document
if (interpreter) {
interpreter._impl->_baseURI = URL::asBaseURL(absUrl);
+ interpreter._impl->_sourceURI = absUrl;
} else {
LOG(ERROR) << "Cannot create interpreter from URI '" << absUrl.asString() << "'";
}
@@ -1310,16 +1306,26 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Node<std::string>& cont
if (content.getNodeType() != Node_base::ELEMENT_NODE)
return;
- if (false) {
- } else if (iequals(TAGNAME(content), _xmlNSPrefix + "onentry") ||
- iequals(TAGNAME(content), _xmlNSPrefix + "onexit") ||
- iequals(TAGNAME(content), _xmlNSPrefix + "finalize") ||
- iequals(TAGNAME(content), _xmlNSPrefix + "transition")) {
+ if (iequals(TAGNAME(content), _xmlNSPrefix + "onentry") ||
+ iequals(TAGNAME(content), _xmlNSPrefix + "onexit") ||
+ iequals(TAGNAME(content), _xmlNSPrefix + "finalize") ||
+ iequals(TAGNAME(content), _xmlNSPrefix + "transition")) {
// --- CONVENIENCE LOOP --------------------------
NodeList<std::string> executable = content.getChildNodes();
for (int i = 0; i < executable.getLength(); i++) {
executeContent(executable.item(i), rethrow);
}
+ return;
+ }
+
+ // --- MONITOR: beforeExecutingContent ------------------------------
+ for(monIter_t monIter = _monitors.begin(); monIter != _monitors.end(); monIter++) {
+ try {
+ (*monIter)->beforeExecutingContent(shared_from_this(), content);
+ } USCXML_MONITOR_CATCH_BLOCK(beforeExecutingContent)
+ }
+
+ if (false) {
} else if (iequals(TAGNAME(content), _xmlNSPrefix + "raise")) {
// --- RAISE --------------------------
if (HAS_ATTR(content, "event")) {
@@ -1453,6 +1459,9 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Node<std::string>& cont
}
} catch (Event exception) {
// script failed to download
+ if (exception.name == "error.communication") {
+ throw exception; // terminate test329
+ }
receive(exception);
return;
}
@@ -1526,6 +1535,14 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Node<std::string>& cont
}
execContent.exitElement(content);
}
+
+ // --- MONITOR: afterExecutingContent ------------------------------
+ for(monIter_t monIter = _monitors.begin(); monIter != _monitors.end(); monIter++) {
+ try {
+ (*monIter)->afterExecutingContent(shared_from_this(), content);
+ } USCXML_MONITOR_CATCH_BLOCK(afterExecutingContent)
+ }
+
}
void InterpreterImpl::returnDoneEvent(const Arabica::DOM::Node<std::string>& state) {
diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h
index 94c5d74..f6b1783 100644
--- a/src/uscxml/Interpreter.h
+++ b/src/uscxml/Interpreter.h
@@ -98,7 +98,7 @@ enum Capabilities {
class USCXML_API InterpreterOptions {
public:
- bool useDot;
+ bool withDebugger;
bool verbose;
bool withHTTP;
bool withHTTPS;
@@ -126,7 +126,7 @@ public:
protected:
InterpreterOptions() :
- useDot(false),
+ withDebugger(false),
verbose(false),
withHTTP(true),
withHTTPS(true),
@@ -171,12 +171,19 @@ public:
_monitors.erase(monitor);
}
- void setBaseURI(std::string baseURI) {
- _baseURI = URL(baseURI);
+ void setSourceURI(std::string sourceURI) {
+ _sourceURI = URL(sourceURI);
+
+ URL baseURI(sourceURI);
+ URL::toBaseURL(baseURI);
+ _baseURI = baseURI;
}
URL getBaseURI() {
return _baseURI;
}
+ URL getSourceURI() {
+ return _sourceURI;
+ }
void setCmdLineOptions(std::map<std::string, std::string> params);
Data getCmdLineOptions() {
@@ -213,6 +220,11 @@ public:
return _nameSpaceInfo[ns] + ":";
return "";
}
+
+ Arabica::XPath::NodeSet<std::string> getNodeSetForXPath(const std::string& xpathExpr) {
+ return _xpath.evaluate(xpathExpr, _scxml).asNodeSet();
+ }
+
void setNameSpaceInfo(const std::map<std::string, std::string> nameSpaceInfo);
std::map<std::string, std::string> getNameSpaceInfo() {
return _nameSpaceInfo;
@@ -339,6 +351,7 @@ protected:
tthread::recursive_mutex _pluginMutex;
URL _baseURI;
+ URL _sourceURI;
Arabica::DOM::Document<std::string> _document;
Arabica::DOM::Element<std::string> _scxml;
Arabica::XPath::XPath<std::string> _xpath;
@@ -478,8 +491,11 @@ public:
return _impl->removeMonitor(monitor);
}
- void setBaseURI(std::string baseURI) {
- return _impl->setBaseURI(baseURI);
+ void setSourceURI(std::string sourceURI) {
+ return _impl->setSourceURI(sourceURI);
+ }
+ URL getSourceURI() {
+ return _impl->getSourceURI();
}
URL getBaseURI() {
return _impl->getBaseURI();
@@ -527,7 +543,10 @@ public:
std::string getXMLPrefixForNS(const std::string& ns) {
return _impl->getXMLPrefixForNS(ns);
}
-
+ Arabica::XPath::NodeSet<std::string> getNodeSetForXPath(const std::string& xpathExpr) {
+ return _impl->getNodeSetForXPath(xpathExpr);
+ }
+
void inline receiveInternal(const Event& event) {
return _impl->receiveInternal(event);
}
@@ -709,6 +728,9 @@ public:
virtual void beforeExitingState(Interpreter interpreter, const Arabica::DOM::Element<std::string>& state) {}
virtual void afterExitingState(Interpreter interpreter, const Arabica::DOM::Element<std::string>& state) {}
+ virtual void beforeExecutingContent(Interpreter interpreter, const Arabica::DOM::Node<std::string>& content) {}
+ virtual void afterExecutingContent(Interpreter interpreter, const Arabica::DOM::Node<std::string>& content) {}
+
virtual void beforeUninvoking(Interpreter interpreter, const Arabica::DOM::Element<std::string>& invokeElem, const std::string& invokeid) {}
virtual void afterUninvoking(Interpreter interpreter, const Arabica::DOM::Element<std::string>& invokeElem, const std::string& invokeid) {}
diff --git a/src/uscxml/Message.cpp b/src/uscxml/Message.cpp
index aeb0027..33dca84 100644
--- a/src/uscxml/Message.cpp
+++ b/src/uscxml/Message.cpp
@@ -430,6 +430,7 @@ Data Data::fromJSON(const std::string& jsonString) {
std::string value = trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start);
if (dataStack.back()->type == Data::VERBATIM) {
boost::replace_all(value, "\\\"", "\"");
+ boost::replace_all(value, "\\n", "\n");
}
dataStack.back()->atom = value;
dataStack.pop_back();
@@ -728,10 +729,11 @@ std::string Data::toJSON(const Data& data) {
// escape string
if (false) {
} else if (data.atom[i] == '"') {
- os << '\\';
- os << data.atom[i];
+ os << "\\\"";
} else if (data.atom[i] == '\n') {
os << "\\n";
+ } else if (data.atom[i] == '\t') {
+ os << "\\t";
} else {
os << data.atom[i];
}
@@ -746,6 +748,7 @@ std::string Data::toJSON(const Data& data) {
std::string xmlSer = xmlSerSS.str();
boost::replace_all(xmlSer, "\"", "\\\"");
boost::replace_all(xmlSer, "\n", "\\n");
+ boost::replace_all(xmlSer, "\t", "\\t");
os << "\"" << xmlSer << "\"";
} else {
if (data.type == Data::VERBATIM) {
diff --git a/src/uscxml/debug/Breakpoint.cpp b/src/uscxml/debug/Breakpoint.cpp
index f64efad..58cf295 100644
--- a/src/uscxml/debug/Breakpoint.cpp
+++ b/src/uscxml/debug/Breakpoint.cpp
@@ -19,10 +19,12 @@
#include "uscxml/debug/Breakpoint.h"
#include "uscxml/Interpreter.h"
+#include "uscxml/DOMUtils.h"
namespace uscxml {
Breakpoint::Breakpoint(const Data& data) {
+ enabled = true;
subject = UNDEF_SUBJECT;
when = UNDEF_WHEN;
action = UNDEF_ACTION;
@@ -65,7 +67,7 @@ Breakpoint::Breakpoint(const Data& data) {
subject = EVENT;
} else if (data["subject"].atom == "invoker") {
subject = INVOKER;
- } else if (data["subject"].atom == "exec") {
+ } else if (data["subject"].atom == "executable") {
subject = EXECUTABLE;
}
}
@@ -82,6 +84,12 @@ Breakpoint::Breakpoint(const Data& data) {
if (data.hasKey("eventName"))
eventName = data["eventName"].atom;
+ if (data.hasKey("executableName"))
+ executableName = data["executableName"].atom;
+
+ if (data.hasKey("executableXPath"))
+ executableXPath = data["executableXPath"].atom;
+
if (data.hasKey("stateId"))
stateId = data["stateId"].atom;
@@ -116,7 +124,7 @@ Data Breakpoint::toData() const {
data.compound["subject"] = Data("invoker", Data::VERBATIM);
break;
case EXECUTABLE:
- data.compound["subject"] = Data("exec", Data::VERBATIM);
+ data.compound["subject"] = Data("executable", Data::VERBATIM);
break;
default:
break;
@@ -162,6 +170,16 @@ Data Breakpoint::toData() const {
if (eventName.length() > 0)
data.compound["eventName"] = Data(eventName, Data::VERBATIM);
+ if (executableName.length() > 0)
+ data.compound["executableName"] = Data(executableName, Data::VERBATIM);
+
+ if (executableXPath.length() > 0) {
+ data.compound["executableXPath"] = Data(executableXPath, Data::VERBATIM);
+ }
+
+ if (element)
+ data.compound["xpath"] = Data(DOMUtils::xPathForNode(element, "*"), Data::VERBATIM);
+
if (stateId.length() > 0)
data.compound["stateId"] = Data(stateId, Data::VERBATIM);
@@ -177,34 +195,31 @@ Data Breakpoint::toData() const {
return data;
}
-bool Breakpoint::matches(const Breakpoint& other) const {
+bool Breakpoint::matches(Interpreter interpreter, const Breakpoint& other) const {
// would we match the given breakpoint?
if (subject != UNDEF_SUBJECT &&
- other.subject != UNDEF_SUBJECT &&
other.subject != subject)
return false; // subject does not match
if (when != UNDEF_WHEN &&
- other.when != UNDEF_WHEN &&
other.when != when)
return false; // time does not match
if (action != UNDEF_ACTION &&
- other.action != UNDEF_ACTION &&
other.action != action)
return false; // action does not match
// when we have a qualifier it has to match
- if(invokeId.length() > 0 && !InterpreterImpl::nameMatch(invokeId, other.invokeId)) {
+ if(invokeId.length() > 0 && invokeId != other.invokeId) {
return false;
}
- if(invokeType.length() > 0 && !InterpreterImpl::nameMatch(invokeType, other.invokeType)) {
+ if(invokeType.length() > 0 && invokeType != other.invokeType) {
return false;
}
- if(stateId.length() > 0 && !InterpreterImpl::nameMatch(stateId, other.stateId)) {
+ if(stateId.length() > 0 && stateId != other.stateId) {
return false;
}
@@ -212,19 +227,39 @@ bool Breakpoint::matches(const Breakpoint& other) const {
return false;
}
- if(transSource.length() > 0 && !InterpreterImpl::nameMatch(transSource, other.transSource)) {
+ if(executableName.length() > 0 && executableName != other.executableName) {
return false;
}
- if(transTarget.length() > 0 && !InterpreterImpl::nameMatch(transTarget, other.transTarget)) {
+ if(executableXPath.length()) {
+ Arabica::XPath::NodeSet<std::string> nodes;
+ try {
+ nodes = interpreter.getNodeSetForXPath(executableXPath);
+ } catch (...) {
+ return false;
+ }
+ return Interpreter::isMember(other.element, nodes);
+ }
+
+ if(transSource.length() > 0 && transSource != other.transSource) {
return false;
}
-
- return true;
-}
+ if(transTarget.length() > 0 && transTarget != other.transTarget) {
+ return false;
+ }
-bool Breakpoint::isValid() {
+ if (condition.length() > 0) {
+ try {
+ DataModel dm = interpreter.getDataModel();
+ if (!dm || !dm.evalAsBool(condition)) {
+ return false;
+ }
+ } catch (...) {
+ return false;
+ }
+ }
+
return true;
}
diff --git a/src/uscxml/debug/Breakpoint.h b/src/uscxml/debug/Breakpoint.h
index b2861d8..cd6fea5 100644
--- a/src/uscxml/debug/Breakpoint.h
+++ b/src/uscxml/debug/Breakpoint.h
@@ -21,6 +21,7 @@
#define BREAKPOINT_H_VR7K7T1X
#include "uscxml/Message.h"
+#include "uscxml/Interpreter.h"
namespace uscxml {
@@ -39,35 +40,49 @@ public:
UNDEF_ACTION, ENTER, EXIT, INVOKE, UNINVOKE
};
- Breakpoint() {}
+ Breakpoint() {
+ subject = UNDEF_SUBJECT;
+ when = UNDEF_WHEN;
+ action = UNDEF_ACTION;
+ }
Breakpoint(const Data& data);
// would we match the given breakpoint as well?
- bool matches(const Breakpoint& other) const;
-
- bool isValid();
-
+ bool matches(Interpreter interpreter, const Breakpoint& other) const;
+
Data toData() const;
bool operator<(const Breakpoint& other) const {
- return (origData < other.origData);
+ return (toData() < other.toData());
}
+ operator bool() {
+ return (subject != UNDEF_SUBJECT ||
+ when != UNDEF_WHEN ||
+ action != UNDEF_ACTION);
+ }
+
+ mutable bool enabled;
+
When when;
Subject subject;
Action action;
+ Arabica::DOM::Element<std::string> element;
+
std::string invokeId;
std::string invokeType;
std::string eventName;
+ std::string executableName;
+ std::string executableXPath;
+
std::string stateId;
std::string transSource;
std::string transTarget;
std::string condition;
- Data origData;
};
}
diff --git a/src/uscxml/debug/DebugSession.cpp b/src/uscxml/debug/DebugSession.cpp
new file mode 100644
index 0000000..46b414f
--- /dev/null
+++ b/src/uscxml/debug/DebugSession.cpp
@@ -0,0 +1,378 @@
+/**
+ * @file
+ * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de)
+ * @copyright Simplified BSD
+ *
+ * @cond
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the FreeBSD license as published by the FreeBSD
+ * project.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * You should have received a copy of the FreeBSD license along with this
+ * program. If not, see <http://www.opensource.org/licenses/bsd-license>.
+ * @endcond
+ */
+
+#include "uscxml/debug/DebugSession.h"
+#include "uscxml/debug/Debugger.h"
+
+namespace uscxml {
+
+void DebugSession::checkBreakpoints(const std::list<Breakpoint> qualifiedBreakpoints) {
+ std::list<Breakpoint>::const_iterator qualifiedBreakpointIter = qualifiedBreakpoints.begin();
+
+ if (!_breakpointsEnabled)
+ return;
+
+ while(qualifiedBreakpointIter != qualifiedBreakpoints.end()) {
+ const Breakpoint& qualifiedBreakpoint = *qualifiedBreakpointIter++;
+
+ // check if one of the user-supplied breakpoints match
+ bool userBreakpointMatched = false;
+ Data replyData;
+
+ if (_skipTo) {
+ if (_skipTo.matches(_interpreter, qualifiedBreakpoint)) {
+ replyData.compound["breakpoint"] = _skipTo.toData();
+ replyData.compound["qualified"] = qualifiedBreakpoint.toData();
+ breakExecution(replyData);
+ _skipTo = Breakpoint();
+ }
+ continue;
+ }
+
+ std::set<Breakpoint>::const_iterator breakpointIter = _breakPoints.begin();
+ while(breakpointIter != _breakPoints.end()) {
+ const Breakpoint& breakpoint = *breakpointIter++;
+ if (!breakpoint.enabled)
+ continue;
+ if (breakpoint.matches(_interpreter, qualifiedBreakpoint)) {
+ // do we have a condition?
+
+ replyData.compound["breakpoint"] = breakpoint.toData();
+ replyData.compound["qualified"] = qualifiedBreakpoint.toData();
+
+ userBreakpointMatched = true;
+ breakExecution(replyData);
+ }
+ }
+ if (_isStepping && !userBreakpointMatched) {
+ replyData.compound["qualified"] = qualifiedBreakpoint.toData();
+ breakExecution(replyData);
+
+ }
+ }
+}
+
+void DebugSession::breakExecution(Data replyData) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
+
+ Arabica::XPath::NodeSet<std::string> basicConf = _interpreter.getBasicConfiguration();
+ for (int i = 0; i < basicConf.size(); i++) {
+ Arabica::DOM::Element<std::string> element = Arabica::DOM::Element<std::string>(basicConf[i]);
+ if (element.hasAttribute("id")) {
+ replyData.compound["basicStates"].array.push_back(Data(element.getAttribute("id"), Data::VERBATIM));
+ }
+ }
+
+ Arabica::XPath::NodeSet<std::string> activeConf = _interpreter.getConfiguration();
+ for (int i = 0; i < activeConf.size(); i++) {
+ Arabica::DOM::Element<std::string> element = Arabica::DOM::Element<std::string>(activeConf[i]);
+ if (element.hasAttribute("id")) {
+ replyData.compound["activeStates"].array.push_back(Data(element.getAttribute("id"), Data::VERBATIM));
+ }
+ }
+
+ replyData.compound["replyType"] = Data("breakpoint", Data::VERBATIM);
+ _debugger->pushData(shared_from_this(), replyData);
+ _resumeCond.wait(_mutex);
+}
+
+Data DebugSession::debugPrepare(const Data& data) {
+ Data replyData;
+
+ if (!data.hasKey("xml") && !data.hasKey("url")) {
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ replyData.compound["reason"] = Data("No XML or URL given", Data::VERBATIM);
+ return replyData;
+ }
+
+ debugStop(data);
+
+ _isAttached = false;
+
+ if (data.hasKey("xml")) {
+ _interpreter = Interpreter::fromXML(data["xml"].atom);
+ } else if (data.hasKey("url")) {
+ _interpreter = Interpreter::fromURI(data["url"].atom);
+ } else {
+ _interpreter = Interpreter();
+ }
+
+ if (_interpreter) {
+ // register ourself as a monitor
+ _interpreter.addMonitor(_debugger);
+ _debugger->attachSession(_interpreter, shared_from_this());
+ if (data.hasKey("url")) {
+ // this allows to resolve relative external reources
+ _interpreter.setSourceURI(data["url"].atom);
+ }
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+ } else {
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ }
+
+ return replyData;
+}
+
+Data DebugSession::debugAttach(const Data& data) {
+ Data replyData;
+ _isAttached = true;
+
+ if (!data.hasKey("attach")) {
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ replyData.compound["reason"] = Data("No id to attach to given", Data::VERBATIM);
+ return replyData;
+ }
+
+ std::string interpreterId = data["attach"].atom;
+ bool interpreterFound = false;
+
+ // find interpreter for sessionid
+ std::map<std::string, boost::weak_ptr<InterpreterImpl> > instances = Interpreter::getInstances();
+ for (std::map<std::string, boost::weak_ptr<InterpreterImpl> >::iterator instIter = instances.begin();
+ instIter != instances.end();
+ instIter++) {
+
+ boost::shared_ptr<InterpreterImpl> instance = instIter->second.lock();
+ if (instance && instance->getSessionId() == interpreterId) {
+ _interpreter = instance;
+ _debugger->attachSession(_interpreter, shared_from_this());
+ interpreterFound = true;
+ break;
+ }
+ }
+
+ if (!interpreterFound) {
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ replyData.compound["reason"] = Data("No interpreter with given id found", Data::VERBATIM);
+ } else {
+ replyData.compound["xml"].node = _interpreter.getDocument();
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+ }
+
+ return replyData;
+}
+
+Data DebugSession::debugDetach(const Data& data) {
+ Data replyData;
+ _isAttached = false;
+
+ _debugger->detachSession(_interpreter);
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+ return replyData;
+}
+
+Data DebugSession::debugStart(const Data& data) {
+ Data replyData;
+
+ if (_isAttached) {
+ replyData.compound["reason"] = Data("Already started when attached", Data::VERBATIM);
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ } else if (!_interpreter) {
+ replyData.compound["reason"] = Data("No interpreter attached or loaded", Data::VERBATIM);
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ } else {
+ _interpreter.start();
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+ }
+
+ return replyData;
+}
+
+Data DebugSession::debugStop(const Data& data) {
+ Data replyData;
+
+ if (_interpreter) {
+ // detach from old intepreter
+ _debugger->detachSession(_interpreter);
+ }
+
+ if (_interpreter && !_isAttached)
+ _interpreter.stop();
+ // unblock
+ _resumeCond.notify_all();
+
+ _skipTo = Breakpoint();
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+
+ // calls destructor
+ _interpreter = Interpreter();
+
+ return replyData;
+}
+
+Data DebugSession::debugStep(const Data& data) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
+
+ stepping(true);
+ _resumeCond.notify_one();
+
+ Data replyData;
+ if (_interpreter) {
+ // register ourself as a monitor
+ if (!_interpreter.isRunning())
+ _interpreter.start();
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+ } else {
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ }
+ return replyData;
+}
+
+Data DebugSession::debugResume(const Data& data) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
+
+ stepping(false);
+
+ Data replyData;
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+
+ _resumeCond.notify_one();
+ return replyData;
+}
+
+
+Data DebugSession::debugPause(const Data& data) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
+
+ _skipTo = Breakpoint();
+ stepping(true);
+
+ Data replyData;
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+
+ return replyData;
+}
+
+Data DebugSession::skipToBreakPoint(const Data& data) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
+
+ _skipTo = Breakpoint(data);
+
+ Data replyData;
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+
+ _resumeCond.notify_one();
+ return replyData;
+}
+
+Data DebugSession::addBreakPoint(const Data& data) {
+ Breakpoint breakpoint(data);
+
+ Data replyData;
+ if (_breakPoints.find(breakpoint) == _breakPoints.end()) {
+ _breakPoints.insert(breakpoint);
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+
+ } else {
+ replyData.compound["reason"] = Data("Breakpoint already exists", Data::VERBATIM);
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ }
+ return replyData;
+}
+
+Data DebugSession::removeBreakPoint(const Data& data) {
+ Breakpoint breakpoint(data);
+
+ Data replyData;
+ if (_breakPoints.find(breakpoint) != _breakPoints.end()) {
+ _breakPoints.erase(breakpoint);
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+ } else {
+ replyData.compound["reason"] = Data("No such breakpoint", Data::VERBATIM);
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ }
+ return replyData;
+}
+
+Data DebugSession::enableBreakPoint(const Data& data) {
+ Breakpoint breakpoint(data);
+
+ Data replyData;
+ if (_breakPoints.find(breakpoint) != _breakPoints.end()) {
+ _breakPoints.find(breakpoint)->enabled = true;
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+ } else {
+ replyData.compound["reason"] = Data("No such breakpoint", Data::VERBATIM);
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ }
+
+ return replyData;
+}
+Data DebugSession::disableBreakPoint(const Data& data) {
+ Breakpoint breakpoint(data);
+
+ Data replyData;
+ if (_breakPoints.find(breakpoint) != _breakPoints.end()) {
+ _breakPoints.find(breakpoint)->enabled = false;
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+ } else {
+ replyData.compound["reason"] = Data("No such breakpoint", Data::VERBATIM);
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ }
+
+ return replyData;
+}
+Data DebugSession::enableAllBreakPoints() {
+ Data replyData;
+
+ _breakpointsEnabled = true;
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+
+ return replyData;
+}
+Data DebugSession::disableAllBreakPoints() {
+ Data replyData;
+
+ _breakpointsEnabled = false;
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+
+ return replyData;
+}
+
+Data DebugSession::debugEval(const Data& data) {
+ Data replyData;
+
+ if (!data.hasKey("expression")) {
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ replyData.compound["reason"] = Data("No expression given", Data::VERBATIM);
+ return replyData;
+ }
+
+ std::string expr = data["expression"].atom;
+
+ if (!_interpreter) {
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ replyData.compound["reason"] = Data("No interpreter running", Data::VERBATIM);
+ } else if (!_interpreter.getDataModel()) {
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ replyData.compound["reason"] = Data("No datamodel available", Data::VERBATIM);
+ } else {
+ try {
+ replyData.compound["eval"] = _interpreter.getDataModel().getStringAsData(expr);
+ } catch (Event e) {
+ replyData.compound["eval"] = e.data;
+ replyData.compound["eval"].compound["error"] = Data(e.name, Data::VERBATIM);
+ }
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+ }
+ return replyData;
+}
+
+
+} \ No newline at end of file
diff --git a/src/uscxml/debug/DebugSession.h b/src/uscxml/debug/DebugSession.h
new file mode 100644
index 0000000..12f1d93
--- /dev/null
+++ b/src/uscxml/debug/DebugSession.h
@@ -0,0 +1,99 @@
+/**
+ * @file
+ * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de)
+ * @copyright Simplified BSD
+ *
+ * @cond
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the FreeBSD license as published by the FreeBSD
+ * project.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * You should have received a copy of the FreeBSD license along with this
+ * program. If not, see <http://www.opensource.org/licenses/bsd-license>.
+ * @endcond
+ */
+
+#ifndef DEBUGSESSION_H_M8YHEGV6
+#define DEBUGSESSION_H_M8YHEGV6
+
+#include "uscxml/debug/Breakpoint.h"
+#include "uscxml/Interpreter.h"
+#include <time.h>
+
+namespace uscxml {
+
+class Debugger;
+
+class USCXML_API DebugSession : public boost::enable_shared_from_this<DebugSession> {
+public:
+ DebugSession() {
+ _isStepping = false;
+ _isAttached = false;
+ _breakpointsEnabled = true;
+ _markedForDeletion = false;
+ _debugger = NULL;
+ }
+
+ void stepping(bool enable) {
+ _isStepping = enable;
+ }
+
+ void checkBreakpoints(const std::list<Breakpoint> qualifiedBreakpoints);
+
+ Data debugPrepare(const Data& data);
+ Data debugAttach(const Data& data);
+ Data debugDetach(const Data& data);
+ Data debugStart(const Data& data);
+ Data debugStop(const Data& data);
+ Data debugStep(const Data& data);
+ Data debugResume(const Data& data);
+ Data debugPause(const Data& data);
+ Data skipToBreakPoint(const Data& data);
+ Data addBreakPoint(const Data& data);
+ Data removeBreakPoint(const Data& data);
+ Data enableBreakPoint(const Data& data);
+ Data disableBreakPoint(const Data& data);
+ Data enableAllBreakPoints();
+ Data disableAllBreakPoints();
+ Data debugEval(const Data& data);
+
+ void setDebugger(Debugger* debugger) {
+ _debugger = debugger;
+ }
+
+ Interpreter getInterpreter() {
+ return _interpreter;
+ }
+
+ void markForDeletion(bool mark) {
+ _markedForDeletion = mark;
+ }
+
+protected:
+ void breakExecution(Data replyData);
+
+ bool _isStepping;
+ bool _isAttached;
+ bool _breakpointsEnabled;
+
+ tthread::condition_variable _resumeCond;
+ tthread::recursive_mutex _runMutex;
+ tthread::recursive_mutex _mutex;
+
+ bool _markedForDeletion;
+ Debugger* _debugger;
+ Interpreter _interpreter;
+ std::set<Breakpoint> _breakPoints;
+ Breakpoint _skipTo;
+
+};
+
+
+}
+
+
+#endif /* end of include guard: DEBUGSESSION_H_M8YHEGV6 */
diff --git a/src/uscxml/debug/Debugger.cpp b/src/uscxml/debug/Debugger.cpp
index aa97a22..3a19228 100644
--- a/src/uscxml/debug/Debugger.cpp
+++ b/src/uscxml/debug/Debugger.cpp
@@ -23,9 +23,13 @@
namespace uscxml {
void Debugger::afterCompletion(Interpreter interpreter) {
+ boost::shared_ptr<DebugSession> session = getSession(interpreter);
+ if (!session)
+ return;
+
Data msg;
msg.compound["replyType"] = Data("finished", Data::VERBATIM);
- pushData(msg);
+ pushData(session, msg);
}
std::list<Breakpoint> getQualifiedStateBreakpoints(Interpreter interpreter, const Arabica::DOM::Element<std::string>& state, Breakpoint breakpointTemplate) {
@@ -33,6 +37,7 @@ std::list<Breakpoint> getQualifiedStateBreakpoints(Interpreter interpreter, cons
Breakpoint bp = breakpointTemplate; // copy base as template
bp.stateId = ATTR(state, "id");
+ bp.element = state;
bp.subject = Breakpoint::STATE;
breakpoints.push_back(bp);
@@ -44,6 +49,7 @@ std::list<Breakpoint> getQualifiedInvokeBreakpoints(Interpreter interpreter, con
Breakpoint bp = breakpointTemplate; // copy base as template
bp.subject = Breakpoint::INVOKER;
+ bp.element = invokeElem;
bp.invokeId = invokeId;
if (HAS_ATTR(invokeElem, "type")) {
@@ -67,6 +73,7 @@ std::list<Breakpoint> getQualifiedTransBreakpoints(Interpreter interpreter, cons
Arabica::DOM::Element<std::string> target(targets[j]);
Breakpoint bp = breakpointTemplate; // copy base as template
+ bp.element = transition;
bp.transSource = ATTR(source, "id");
bp.transTarget = ATTR(target, "id");
bp.subject = Breakpoint::TRANSITION;
@@ -83,6 +90,12 @@ void Debugger::beforeTakingTransition(Interpreter interpreter, const Arabica::DO
void Debugger::afterTakingTransition(Interpreter interpreter, const Arabica::DOM::Element<std::string>& transition) {
handleTransition(interpreter, transition, Breakpoint::AFTER);
}
+void Debugger::beforeExecutingContent(Interpreter interpreter, const Arabica::DOM::Node<std::string>& content) {
+ handleExecutable(interpreter, Arabica::DOM::Element<std::string>(content), Breakpoint::BEFORE);
+}
+void Debugger::afterExecutingContent(Interpreter interpreter, const Arabica::DOM::Node<std::string>& content) {
+ handleExecutable(interpreter, Arabica::DOM::Element<std::string>(content), Breakpoint::AFTER);
+}
void Debugger::beforeExitingState(Interpreter interpreter, const Arabica::DOM::Element<std::string>& state) {
handleState(interpreter, state, Breakpoint::BEFORE, Breakpoint::EXIT);
}
@@ -120,9 +133,34 @@ void Debugger::beforeProcessingEvent(Interpreter interpreter, const Event& event
handleEvent(interpreter, event, Breakpoint::BEFORE);
}
+void Debugger::handleExecutable(Interpreter interpreter,
+ const Arabica::DOM::Element<std::string>& execContentElem,
+ Breakpoint::When when) {
+ if (!interpreter.isRunning())
+ return;
+ boost::shared_ptr<DebugSession> session = getSession(interpreter);
+ if (!session)
+ return;
+
+ std::list<Breakpoint> breakpoints;
+
+ Breakpoint breakpoint;
+ breakpoint.when = when;
+ breakpoint.element = execContentElem;
+ breakpoint.executableName = execContentElem.getLocalName();
+ breakpoint.subject = Breakpoint::EXECUTABLE;
+ breakpoints.push_back(breakpoint);
+
+ session->checkBreakpoints(breakpoints);
+
+}
+
void Debugger::handleEvent(Interpreter interpreter, const Event& event, Breakpoint::When when) {
if (!interpreter.isRunning())
return;
+ boost::shared_ptr<DebugSession> session = getSession(interpreter);
+ if (!session)
+ return;
std::list<Breakpoint> breakpoints;
@@ -132,13 +170,16 @@ void Debugger::handleEvent(Interpreter interpreter, const Event& event, Breakpoi
breakpoint.subject = Breakpoint::EVENT;
breakpoints.push_back(breakpoint);
- checkBreakpoints(interpreter, breakpoints);
+ session->checkBreakpoints(breakpoints);
}
void Debugger::handleStable(Interpreter interpreter, Breakpoint::When when) {
if (!interpreter.isRunning())
return;
+ boost::shared_ptr<DebugSession> session = getSession(interpreter);
+ if (!session)
+ return;
std::list<Breakpoint> breakpoints;
@@ -147,13 +188,16 @@ void Debugger::handleStable(Interpreter interpreter, Breakpoint::When when) {
breakpoint.subject = Breakpoint::STABLE;
breakpoints.push_back(breakpoint);
- checkBreakpoints(interpreter, breakpoints);
+ session->checkBreakpoints(breakpoints);
}
void Debugger::handleMicrostep(Interpreter interpreter, Breakpoint::When when) {
if (!interpreter.isRunning())
return;
-
+ boost::shared_ptr<DebugSession> session = getSession(interpreter);
+ if (!session)
+ return;
+
std::list<Breakpoint> breakpoints;
Breakpoint breakpoint;
@@ -161,86 +205,50 @@ void Debugger::handleMicrostep(Interpreter interpreter, Breakpoint::When when) {
breakpoint.subject = Breakpoint::MICROSTEP;
breakpoints.push_back(breakpoint);
- checkBreakpoints(interpreter, breakpoints);
+ session->checkBreakpoints(breakpoints);
}
void Debugger::handleTransition(Interpreter interpreter, const Arabica::DOM::Element<std::string>& transition, Breakpoint::When when) {
if (!interpreter.isRunning())
return;
+ boost::shared_ptr<DebugSession> session = getSession(interpreter);
+ if (!session)
+ return;
Breakpoint breakpointTemplate;
breakpointTemplate.when = when;
std::list<Breakpoint> qualifiedBreakpoints = getQualifiedTransBreakpoints(interpreter, transition, breakpointTemplate);
- checkBreakpoints(interpreter, qualifiedBreakpoints);
+ session->checkBreakpoints(qualifiedBreakpoints);
}
void Debugger::handleState(Interpreter interpreter, const Arabica::DOM::Element<std::string>& state, Breakpoint::When when, Breakpoint::Action action) {
if (!interpreter.isRunning())
return;
+ boost::shared_ptr<DebugSession> session = getSession(interpreter);
+ if (!session)
+ return;
Breakpoint breakpointTemplate;
breakpointTemplate.when = when;
breakpointTemplate.action = action;
std::list<Breakpoint> qualifiedBreakpoints = getQualifiedStateBreakpoints(interpreter, state, breakpointTemplate);
- checkBreakpoints(interpreter, qualifiedBreakpoints);
+ session->checkBreakpoints(qualifiedBreakpoints);
}
void Debugger::handleInvoke(Interpreter interpreter, const Arabica::DOM::Element<std::string>& invokeElem, const std::string& invokeId, Breakpoint::When when, Breakpoint::Action action) {
if (!interpreter.isRunning())
return;
+ boost::shared_ptr<DebugSession> session = getSession(interpreter);
+ if (!session)
+ return;
Breakpoint breakpointTemplate;
breakpointTemplate.when = when;
breakpointTemplate.action = action;
std::list<Breakpoint> qualifiedBreakpoints = getQualifiedInvokeBreakpoints(interpreter, invokeElem, invokeId, breakpointTemplate);
- checkBreakpoints(interpreter, qualifiedBreakpoints);
-
-}
-
-void Debugger::checkBreakpoints(Interpreter interpreter, const std::list<Breakpoint> qualifiedBreakpoints) {
- std::list<Breakpoint>::const_iterator qualifiedBreakpointIter = qualifiedBreakpoints.begin();
- while(qualifiedBreakpointIter != qualifiedBreakpoints.end()) {
- const Breakpoint& qualifiedBreakpoint = *qualifiedBreakpointIter++;
-
- // check if one of the user-supplied breakpoints match
- bool userBreakpointMatched = false;
- std::set<Breakpoint>::const_iterator breakpointIter = _breakPoints.begin();
- while(breakpointIter != _breakPoints.end()) {
- const Breakpoint& breakpoint = *breakpointIter++;
- if (breakpoint.matches(qualifiedBreakpoint)) {
- Data replyData;
- replyData.compound["breakpoint"] = breakpoint.toData();
- replyData.compound["qualified"] = qualifiedBreakpoint.toData();
-
- userBreakpointMatched = true;
- hitBreakpoint(interpreter, replyData);
- }
- }
- if (_isStepping && !userBreakpointMatched) {
- Data replyData;
- replyData.compound["qualified"] = qualifiedBreakpoint.toData();
- hitBreakpoint(interpreter, replyData);
- }
- }
-}
+ session->checkBreakpoints(qualifiedBreakpoints);
-void Debugger::send(google::LogSeverity severity, const char* full_filename,
- const char* base_filename, int line,
- const struct ::tm* tm_time,
- const char* message, size_t message_len) {
-
- // _sendQueue is thread-safe, not sure about ToString though
-
- LogMessage msg(severity,
- full_filename,
- base_filename,
- line,
- tm_time,
- std::string(message, message_len),
- ToString(severity, base_filename, line, tm_time, message, message_len));
- msg.compound["replyType"] = Data("log", Data::VERBATIM);
- pushData(msg);
}
diff --git a/src/uscxml/debug/Debugger.h b/src/uscxml/debug/Debugger.h
index dfc197d..c49e90f 100644
--- a/src/uscxml/debug/Debugger.h
+++ b/src/uscxml/debug/Debugger.h
@@ -23,45 +23,40 @@
#include "uscxml/Message.h"
#include "uscxml/Interpreter.h"
#include "uscxml/debug/Breakpoint.h"
-
-#include <glog/logging.h>
+#include "uscxml/debug/DebugSession.h"
namespace uscxml {
-class USCXML_API Debugger : public InterpreterMonitor, public google::LogSink {
+class USCXML_API Debugger : public InterpreterMonitor {
public:
Debugger() {
- _isStepping = false;
}
virtual ~Debugger() {}
-
- class LogMessage : public Data {
- public:
- LogMessage(google::LogSeverity severity, const char* full_filename,
- const char* base_filename, int line,
- const struct ::tm* tm_time,
- std::string message, std::string formatted) {
-
- compound["severity"] = severity;
- compound["fullFilename"] = Data(full_filename, Data::VERBATIM);
- compound["baseFilename"] = Data(base_filename, Data::VERBATIM);
- compound["line"] = line;
- compound["message"] = Data(message, Data::VERBATIM);
- compound["time"] = Data(mktime((struct ::tm*)tm_time), Data::INTERPRETED);
- compound["formatted"] = Data(formatted, Data::VERBATIM);
- }
- };
- virtual void pushData(Data pushData) = 0;
- virtual void hitBreakpoint(const Interpreter& interpreter, Data data) = 0;
+ virtual void attachSession(Interpreter interpreter, boost::shared_ptr<DebugSession> session) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_sessionMutex);
+ _sessionForInterpreter[interpreter] = session;
+ }
+
+ virtual void detachSession(Interpreter interpreter) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_sessionMutex);
+ _sessionForInterpreter.erase(interpreter);
+ }
- void stepping(bool enable) {
- _isStepping = enable;
+ virtual boost::shared_ptr<DebugSession> getSession(Interpreter interpreter) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_sessionMutex);
+ if (_sessionForInterpreter.find(interpreter) != _sessionForInterpreter.end())
+ return _sessionForInterpreter[interpreter];
+ return boost::shared_ptr<DebugSession>();
}
+ virtual void pushData(boost::shared_ptr<DebugSession> session, Data pushData) = 0;
+
// InterpreterMonitor
virtual void beforeProcessingEvent(Interpreter interpreter, const Event& event);
virtual void beforeMicroStep(Interpreter interpreter);
+ virtual void beforeExecutingContent(Interpreter interpreter, const Arabica::DOM::Node<std::string>& content);
+ virtual void afterExecutingContent(Interpreter interpreter, const Arabica::DOM::Node<std::string>& content);
virtual void beforeExitingState(Interpreter interpreter, const Arabica::DOM::Element<std::string>& state);
virtual void afterExitingState(Interpreter interpreter, const Arabica::DOM::Element<std::string>& state);
virtual void beforeUninvoking(Interpreter interpreter, const Arabica::DOM::Element<std::string>& invokeElem, const std::string& invokeid);
@@ -79,12 +74,6 @@ public:
virtual void beforeCompletion(Interpreter interpreter) {}
virtual void afterCompletion(Interpreter interpreter);
- // Logsink
- virtual void send(google::LogSeverity severity, const char* full_filename,
- const char* base_filename, int line,
- const struct ::tm* tm_time,
- const char* message, size_t message_len);
-
protected:
void handleTransition(Interpreter interpreter,
@@ -99,14 +88,15 @@ protected:
const std::string& invokeId,
Breakpoint::When when,
Breakpoint::Action action);
+ void handleExecutable(Interpreter interpreter,
+ const Arabica::DOM::Element<std::string>& execContentElem,
+ Breakpoint::When when);
void handleStable(Interpreter interpreter, Breakpoint::When when);
void handleMicrostep(Interpreter interpreter, Breakpoint::When when);
void handleEvent(Interpreter interpreter, const Event& event, Breakpoint::When when);
- void checkBreakpoints(Interpreter interpreter, const std::list<Breakpoint> qualifiedBreakpoints);
- bool _isStepping;
- std::set<Breakpoint> _breakPoints;
-
+ tthread::recursive_mutex _sessionMutex;
+ std::map<Interpreter, boost::shared_ptr<DebugSession> > _sessionForInterpreter;
};
}
diff --git a/src/uscxml/debug/DebuggerServlet.cpp b/src/uscxml/debug/DebuggerServlet.cpp
index e179b8c..55ced75 100644
--- a/src/uscxml/debug/DebuggerServlet.cpp
+++ b/src/uscxml/debug/DebuggerServlet.cpp
@@ -23,25 +23,30 @@
namespace uscxml {
-void DebuggerServlet::pushData(Data pushData) {
+void DebuggerServlet::pushData(boost::shared_ptr<DebugSession> session, Data pushData) {
std::cout << "trying to push " << pushData["replyType"].atom << std::endl;
- _sendQueue.push(pushData);
- serverPushData();
+
+ if (!session) {
+ if (_sendQueues.size() > 0) // logging is not aware of its interpreter
+ _sendQueues.begin()->second.push(pushData);
+ } else {
+ _sendQueues[session].push(pushData);
+ }
+
+ serverPushData(session);
}
-void DebuggerServlet::serverPushData() {
- tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
-
- if (_sendQueue.isEmpty())
+void DebuggerServlet::serverPushData(boost::shared_ptr<DebugSession> session) {
+ if (_sendQueues[session].isEmpty())
return;
- if (!_clientConn)
+ if (!_clientConns[session])
return;
- Data reply = _sendQueue.pop();
+ Data reply = _sendQueues[session].pop();
std::cout << "pushing " << reply["replyType"].atom << std::endl;
- returnData(_clientConn, reply);
- _clientConn = HTTPServer::Request();
+ returnData(_clientConns[session], reply);
+ _clientConns[session] = HTTPServer::Request();
}
void DebuggerServlet::returnData(const HTTPServer::Request& request, Data replyData) {
@@ -56,17 +61,6 @@ void DebuggerServlet::returnData(const HTTPServer::Request& request, Data replyD
reply.headers["Content-Type"] = "application/json";
HTTPServer::reply(reply);
}
-
-void DebuggerServlet::hitBreakpoint(const Interpreter& interpreter,
- Data data) {
- tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
-
- data.compound["replyType"] = Data("breakpoint", Data::VERBATIM);
- pushData(data);
-
- _resumeCond.wait(_mutex);
- tthread::this_thread::sleep_for(tthread::chrono::milliseconds(200));
-}
bool DebuggerServlet::isCORS(const HTTPServer::Request& request) {
return (request.data["type"].atom == "options" &&
@@ -101,220 +95,164 @@ bool DebuggerServlet::httpRecvRequest(const HTTPServer::Request& request) {
std::cout << request.data["path"] << ": " << request.data["content"] << std::endl;
+ Data replyData;
+ // process request that don't need a session
if (false) {
- } else if (boost::starts_with(request.data["path"].atom, "/poll")) {
- processPoll(request);
- } else if (boost::starts_with(request.data["path"].atom, "/connect")) {
+ } else if (boost::starts_with(request.data["path"].atom, "/debug/connect")) {
processConnect(request);
- } else if (boost::starts_with(request.data["path"].atom, "/disconnect")) {
- processDisconnect(request);
- } else if (boost::starts_with(request.data["path"].atom, "/sessions")) {
+ return true;
+ } else if (boost::starts_with(request.data["path"].atom, "/debug/sessions")) {
processListSessions(request);
- } else if (boost::starts_with(request.data["path"].atom, "/breakpoint/add")) {
- processAddBreakPoint(request);
- } else if (boost::starts_with(request.data["path"].atom, "/breakpoint/remove")) {
- processRemoveBreakPoint(request);
+ return true;
+ }
+
+ // get session or return error
+ if (false) {
+ } else if (!request.data["content"].hasKey("session")) {
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ replyData.compound["reason"] = Data("No session given", Data::VERBATIM);
+ } else if (_sessionForId.find(request.data["content"]["session"].atom) == _sessionForId.end()) {
+ replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ replyData.compound["reason"] = Data("No such session", Data::VERBATIM);
+ }
+ if (replyData) {
+ returnData(request, replyData);
+ return true;
+ }
+
+ boost::shared_ptr<DebugSession> session = _sessionForId[request.data["content"]["session"].atom];
+
+ if (false) {
+ } else if (boost::starts_with(request.data["path"].atom, "/debug/poll")) {
+ // save long-standing client poll
+ _clientConns[session] = request;
+ serverPushData(session);
+
+ } else if (boost::starts_with(request.data["path"].atom, "/debug/disconnect")) {
+ processDisconnect(request);
+
+ } else if (boost::starts_with(request.data["path"].atom, "/debug/breakpoint/enable/all")) {
+ replyData = session->enableAllBreakPoints();
+ } else if (boost::starts_with(request.data["path"].atom, "/debug/breakpoint/disable/all")) {
+ replyData = session->disableAllBreakPoints();
+ } else if (boost::starts_with(request.data["path"].atom, "/debug/breakpoint/skipto")) {
+ replyData = session->skipToBreakPoint(request.data["content"]);
+ } else if (boost::starts_with(request.data["path"].atom, "/debug/breakpoint/add")) {
+ replyData = session->addBreakPoint(request.data["content"]);
+ } else if (boost::starts_with(request.data["path"].atom, "/debug/breakpoint/remove")) {
+ replyData = session->removeBreakPoint(request.data["content"]);
+ } else if (boost::starts_with(request.data["path"].atom, "/debug/breakpoint/enable")) {
+ replyData = session->enableBreakPoint(request.data["content"]);
+ } else if (boost::starts_with(request.data["path"].atom, "/debug/breakpoint/disable")) {
+ replyData = session->disableBreakPoint(request.data["content"]);
+ } else if (boost::starts_with(request.data["path"].atom, "/debug/stop")) {
+ replyData = session->debugStop(request.data["content"]);
} else if (boost::starts_with(request.data["path"].atom, "/debug/prepare")) {
- processDebugPrepare(request);
+ replyData = session->debugPrepare(request.data["content"]);
+ } else if (boost::starts_with(request.data["path"].atom, "/debug/attach")) {
+ replyData = session->debugAttach(request.data["content"]);
} else if (boost::starts_with(request.data["path"].atom, "/debug/start")) {
- processDebugStart(request);
- } else if (boost::starts_with(request.data["path"].atom, "/debug/stop")) {
- processDebugStop(request);
+ replyData = session->debugStart(request.data["content"]);
} else if (boost::starts_with(request.data["path"].atom, "/debug/step")) {
- processDebugStep(request);
+ replyData = session->debugStep(request.data["content"]);
} else if (boost::starts_with(request.data["path"].atom, "/debug/pause")) {
- processDebugPause(request);
+ replyData = session->debugPause(request.data["content"]);
} else if (boost::starts_with(request.data["path"].atom, "/debug/resume")) {
- processDebugResume(request);
+ replyData = session->debugResume(request.data["content"]);
} else if (boost::starts_with(request.data["path"].atom, "/debug/eval")) {
- processDebugEval(request);
+ replyData = session->debugEval(request.data["content"]);
}
- return true;
-}
-
-void DebuggerServlet::processPoll(const HTTPServer::Request& request) {
- tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
- _clientConn = request;
- serverPushData();
-}
-
-void DebuggerServlet::processDebugPrepare(const HTTPServer::Request& request) {
- tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
-
-// std::cout << "clearing all pushes" << std::endl;
-// _sendQueue.clear();
-
- // this will call the destructor if _interpreter already is set
- _resumeCond.notify_all();
- _interpreter = Interpreter::fromXML(request.data["content"].atom);
-
- Data replyData;
- if (_interpreter) {
- // register ourself as a monitor
- _interpreter.addMonitor(this);
- replyData.compound["status"] = Data("success", Data::VERBATIM);
- } else {
- replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ if (replyData) {
+ returnData(request, replyData);
+ return true;
}
- returnData(request, replyData);
+
+ return true;
}
-void DebuggerServlet::processDebugStart(const HTTPServer::Request& request) {
+// someone connected, create a new session
+void DebuggerServlet::processConnect(const HTTPServer::Request& request) {
tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
+ std::string sessionId = UUID::getUUID();
+
+ _sessionForId[sessionId] = boost::shared_ptr<DebugSession>(new DebugSession());
+ _sessionForId[sessionId]->setDebugger(this);
Data replyData;
- if (_interpreter) {
- // register ourself as a monitor
- _interpreter.start();
- replyData.compound["status"] = Data("success", Data::VERBATIM);
- } else {
- replyData.compound["status"] = Data("failure", Data::VERBATIM);
- }
+ replyData.compound["session"] = Data(sessionId, Data::VERBATIM);
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
returnData(request, replyData);
}
-void DebuggerServlet::processDebugStop(const HTTPServer::Request& request) {
-// tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
-
- stepping(false);
+void DebuggerServlet::processDisconnect(const HTTPServer::Request& request) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
Data replyData;
- if (_interpreter) {
- _interpreter.stop();
- _resumeCond.notify_all(); // unblock breakpoints
- _interpreter.join();
- _interpreter = Interpreter(); // empty interpreter, calls destructor
- replyData.compound["status"] = Data("success", Data::VERBATIM);
- } else {
- replyData.compound["status"] = Data("failure", Data::VERBATIM);
- replyData.compound["reason"] = Data("Interpreter already stopped", Data::VERBATIM);
- }
- returnData(request, replyData);
-}
-void DebuggerServlet::processDebugEval(const HTTPServer::Request& request) {
- Data replyData;
- if (!_interpreter) {
- replyData.compound["status"] = Data("failure", Data::VERBATIM);
- replyData.compound["reason"] = Data("No interpreter running", Data::VERBATIM);
- } else if (!_interpreter.getDataModel()) {
+ if (!request.data["content"].hasKey("session")) {
replyData.compound["status"] = Data("failure", Data::VERBATIM);
- replyData.compound["reason"] = Data("No datamodel available", Data::VERBATIM);
- } else if (!request.data["content"].hasKey("expression")) {
- replyData.compound["status"] = Data("failure", Data::VERBATIM);
- replyData.compound["reason"] = Data("No expression given", Data::VERBATIM);
- } else {
- std::string expr = request.data["content"]["expression"].atom;
- try {
- replyData.compound["eval"] = _interpreter.getDataModel().getStringAsData(expr);
- } catch (Event e) {
- replyData.compound["eval"] = e.data;
- replyData.compound["eval"].compound["error"] = Data(e.name, Data::VERBATIM);
- }
- replyData.compound["status"] = Data("success", Data::VERBATIM);
+ replyData.compound["reason"] = Data("No session given", Data::VERBATIM);
+ returnData(request, replyData);
}
- returnData(request, replyData);
-}
-void DebuggerServlet::processDebugStep(const HTTPServer::Request& request) {
- tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
+ std::string sessionId = request.data["content"]["session"].atom;
- stepping(true);
- _resumeCond.notify_one();
-
- Data replyData;
- if (_interpreter && !_interpreter.isRunning()) {
- // register ourself as a monitor
- _interpreter.start();
- replyData.compound["status"] = Data("success", Data::VERBATIM);
- } else {
+ if (_sessionForId.find(sessionId) == _sessionForId.end()) {
replyData.compound["status"] = Data("failure", Data::VERBATIM);
+ replyData.compound["reason"] = Data("No such session", Data::VERBATIM);
+ } else {
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
+ detachSession(_sessionForId[sessionId]->getInterpreter());
+ _sessionForId[sessionId]->debugStop(request.data["content"]);
+ _clientConns.erase(_sessionForId[sessionId]);
+ _sendQueues.erase(_sessionForId[sessionId]);
+ _sessionForId.erase(sessionId);
}
- returnData(request, replyData);
-
-}
-
-void DebuggerServlet::processDebugResume(const HTTPServer::Request& request) {
- tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
-
- stepping(false);
-
- Data replyData;
- replyData.compound["status"] = Data("success", Data::VERBATIM);
- returnData(request, replyData);
-
- _resumeCond.notify_one();
-}
-
-void DebuggerServlet::processDebugPause(const HTTPServer::Request& request) {
- tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
-
- Data replyData;
- replyData.compound["status"] = Data("success", Data::VERBATIM);
- returnData(request, replyData);
-}
-
-void DebuggerServlet::processConnect(const HTTPServer::Request& request) {
- tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
- _sessionId = UUID::getUUID();
- _breakPoints.clear();
-// _sendQueue.clear();
- Data replyData;
- replyData.compound["session"] = Data(_sessionId, Data::VERBATIM);
- replyData.compound["status"] = Data("success", Data::VERBATIM);
returnData(request, replyData);
}
void DebuggerServlet::processListSessions(const HTTPServer::Request& request) {
Data replyData;
-
- // TODO: return actual data
- Data sessionData;
- sessionData.compound["name"] = Data("Not actually a Session", Data::VERBATIM);
- sessionData.compound["id"] = Data("23452523-wg23g2g2-234t2g-23g2g", Data::VERBATIM);
- replyData.compound["sessions"].array.push_back(sessionData);
-
- sessionData.compound["name"] = Data("But returned from the server!", Data::VERBATIM);
- sessionData.compound["id"] = Data("swfgsgfw-g232vqvq-234t2g-23g2g", Data::VERBATIM);
- replyData.compound["sessions"].array.push_back(sessionData);
-
- replyData.compound["status"] = Data("success", Data::VERBATIM);
- returnData(request, replyData);
-}
-
-void DebuggerServlet::processDisconnect(const HTTPServer::Request& request) {
- Data replyData;
- replyData.compound["status"] = Data("success", Data::VERBATIM);
- returnData(request, replyData);
-}
-
-void DebuggerServlet::processAddBreakPoint(const HTTPServer::Request& request) {
- Breakpoint breakPoint(request.data["content"]);
- Data replyData;
- if (breakPoint.isValid()) {
- replyData.compound["status"] = Data("success", Data::VERBATIM);
-
- if (_breakPoints.find(breakPoint) == _breakPoints.end()) {
- _breakPoints.insert(breakPoint);
+
+ std::map<std::string, boost::weak_ptr<InterpreterImpl> > instances = Interpreter::getInstances();
+ for (std::map<std::string, boost::weak_ptr<InterpreterImpl> >::iterator instIter = instances.begin();
+ instIter != instances.end();
+ instIter++) {
+
+ boost::shared_ptr<InterpreterImpl> instance = instIter->second.lock();
+ if (instance) {
+ Data sessionData;
+ sessionData.compound["name"] = Data(instance->getName(), Data::VERBATIM);
+ sessionData.compound["id"] = Data(instance->getSessionId(), Data::VERBATIM);
+ sessionData.compound["source"] = Data(instance->getSourceURI().asString(), Data::VERBATIM);
+ sessionData.compound["xml"].node = instance->getDocument();
+
+ replyData.compound["sessions"].array.push_back(sessionData);
}
- } else {
- replyData.compound["status"] = Data("failure", Data::VERBATIM);
}
+
+ replyData.compound["status"] = Data("success", Data::VERBATIM);
returnData(request, replyData);
}
-void DebuggerServlet::processRemoveBreakPoint(const HTTPServer::Request& request) {
- Breakpoint breakPoint(request.data["content"]);
- Data replyData;
- if (_breakPoints.erase(breakPoint) > 0) {
- replyData.compound["status"] = Data("success", Data::VERBATIM);
- } else {
- replyData.compound["message"] = Data("No such breakpoint", Data::VERBATIM);
- replyData.compound["status"] = Data("failure", Data::VERBATIM);
- }
- returnData(request, replyData);
+void DebuggerServlet::send(google::LogSeverity severity, const char* full_filename,
+ const char* base_filename, int line,
+ const struct ::tm* tm_time,
+ const char* message, size_t message_len) {
+
+ // _sendQueue is thread-safe, not sure about ToString though
+
+ LogMessage msg(severity,
+ full_filename,
+ base_filename,
+ line,
+ tm_time,
+ std::string(message, message_len),
+ ToString(severity, base_filename, line, tm_time, message, message_len));
+ msg.compound["replyType"] = Data("log", Data::VERBATIM);
+ pushData(boost::shared_ptr<DebugSession>(), msg);
}
diff --git a/src/uscxml/debug/DebuggerServlet.h b/src/uscxml/debug/DebuggerServlet.h
index 5cd0be9..ae5178f 100644
--- a/src/uscxml/debug/DebuggerServlet.h
+++ b/src/uscxml/debug/DebuggerServlet.h
@@ -22,6 +22,7 @@
#include "uscxml/Common.h"
#include "getopt.h"
+#include <glog/logging.h>
#include "uscxml/server/HTTPServer.h"
#include "uscxml/Interpreter.h"
@@ -31,8 +32,25 @@
namespace uscxml {
-class USCXML_API DebuggerServlet : public Debugger, public HTTPServlet {
+class USCXML_API DebuggerServlet : public Debugger, public HTTPServlet, public google::LogSink {
public:
+ class LogMessage : public Data {
+ public:
+ LogMessage(google::LogSeverity severity, const char* full_filename,
+ const char* base_filename, int line,
+ const struct ::tm* tm_time,
+ std::string message, std::string formatted) {
+
+ compound["severity"] = severity;
+ compound["fullFilename"] = Data(full_filename, Data::VERBATIM);
+ compound["baseFilename"] = Data(base_filename, Data::VERBATIM);
+ compound["line"] = line;
+ compound["message"] = Data(message, Data::VERBATIM);
+ compound["time"] = Data(mktime((struct ::tm*)tm_time), Data::INTERPRETED);
+ compound["formatted"] = Data(formatted, Data::VERBATIM);
+ }
+ };
+
virtual ~DebuggerServlet() {}
// from Debugger
@@ -46,36 +64,43 @@ public:
_url = url;
}
- void pushData(Data pushData);
+ void pushData(boost::shared_ptr<DebugSession> session, Data pushData);
void returnData(const HTTPServer::Request& request, Data replyData);
- void hitBreakpoint(const Interpreter& interpreter,
- Data data);
-
- void processDebugEval(const HTTPServer::Request& request);
- void processDebugPrepare(const HTTPServer::Request& request);
- void processDebugStart(const HTTPServer::Request& request);
- void processDebugStop(const HTTPServer::Request& request);
- void processDebugStep(const HTTPServer::Request& request);
- void processDebugResume(const HTTPServer::Request& request);
- void processDebugPause(const HTTPServer::Request& request);
+ void processDisconnect(const HTTPServer::Request& request);
void processConnect(const HTTPServer::Request& request);
void processListSessions(const HTTPServer::Request& request);
- void processDisconnect(const HTTPServer::Request& request);
- void processAddBreakPoint(const HTTPServer::Request& request);
- void processRemoveBreakPoint(const HTTPServer::Request& request);
- void processPoll(const HTTPServer::Request& request);
+
+// void processDebugPrepare(const HTTPServer::Request& request);
+// void processDebugAttach(const HTTPServer::Request& request);
+// void processDebugStart(const HTTPServer::Request& request);
+// void processDebugStop(const HTTPServer::Request& request);
+
+// void processDebugEval(const HTTPServer::Request& request);
+// void processDebugStart(const HTTPServer::Request& request);
+// void processDebugStop(const HTTPServer::Request& request);
+// void processDebugStep(const HTTPServer::Request& request);
+// void processDebugResume(const HTTPServer::Request& request);
+// void processDebugPause(const HTTPServer::Request& request);
+// void processAddBreakPoint(const HTTPServer::Request& request);
+// void processRemoveBreakPoint(const HTTPServer::Request& request);
+// void processPoll(const HTTPServer::Request& request);
+ // Logsink
+ virtual void send(google::LogSeverity severity, const char* full_filename,
+ const char* base_filename, int line,
+ const struct ::tm* tm_time,
+ const char* message, size_t message_len);
+
protected:
- void serverPushData();
+ void serverPushData(boost::shared_ptr<DebugSession>);
- Interpreter _interpreter;
- std::string _sessionId;
std::string _url;
- HTTPServer::Request _clientConn;
- tthread::condition_variable _resumeCond;
+ std::map<boost::shared_ptr<DebugSession>, HTTPServer::Request> _clientConns;
+ std::map<boost::shared_ptr<DebugSession>, concurrency::BlockingQueue<Data> > _sendQueues;
+ std::map<std::string, boost::shared_ptr<DebugSession> > _sessionForId;
+
tthread::recursive_mutex _mutex;
- concurrency::BlockingQueue<Data> _sendQueue;
};
}
diff --git a/src/uscxml/plugins/ioprocessor/basichttp/BasicHTTPIOProcessor.cpp b/src/uscxml/plugins/ioprocessor/basichttp/BasicHTTPIOProcessor.cpp
index 51e1e28..085d919 100644
--- a/src/uscxml/plugins/ioprocessor/basichttp/BasicHTTPIOProcessor.cpp
+++ b/src/uscxml/plugins/ioprocessor/basichttp/BasicHTTPIOProcessor.cpp
@@ -263,7 +263,7 @@ void BasicHTTPIOProcessor::downloadCompleted(const URL& url) {
std::map<std::string, std::pair<URL, SendRequest> >::iterator reqIter = _sendRequests.begin();
while(reqIter != _sendRequests.end()) {
if (reqIter->second.first == url) {
- // test 513
+ // test513
std::string statusCode = url.getStatusCode();
if (statusCode.length() > 0) {
std::string statusPrefix = statusCode.substr(0,1);
diff --git a/src/uscxml/server/HTTPServer.cpp b/src/uscxml/server/HTTPServer.cpp
index 6c26811..69096fc 100644
--- a/src/uscxml/server/HTTPServer.cpp
+++ b/src/uscxml/server/HTTPServer.cpp
@@ -105,7 +105,7 @@ HTTPServer::HTTPServer(unsigned short port, unsigned short wsPort, SSLConfig* ss
LOG(ERROR) << "HTTP server cannot bind to tcp/" << _port;
}
}
-
+
_wsPort = wsPort;
if (_wsPort > 0) {
_wsHandle = evws_bind_socket(_evws, _wsPort);
diff --git a/test/samples/uscxml/test-dirmon.scxml b/test/samples/uscxml/test-dirmon.scxml
index 872757f..1fba300 100644
--- a/test/samples/uscxml/test-dirmon.scxml
+++ b/test/samples/uscxml/test-dirmon.scxml
@@ -2,7 +2,7 @@
<script src="http://uscxml.tk.informatik.tu-darmstadt.de/scripts/dump.js" />
<state id="start">
<invoke type="dirmon">
- <param name="dir" expr="'.'" />
+ <param name="dir" expr="'./audio'" />
<param name="recurse" expr="'true'" />
<param name="reportexisting" expr="'true'" />
<param name="suffix" expr="'.scxml'" />
diff --git a/test/samples/w3c/draft/calc.scxml b/test/samples/w3c/draft/calc.scxml
new file mode 100644
index 0000000..e854401
--- /dev/null
+++ b/test/samples/w3c/draft/calc.scxml
@@ -0,0 +1,158 @@
+<?xml version="1.0" ?>
+<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"
+ initial="on" datamodel="ecmascript" name="calc">
+ <datamodel>
+ <data id="long_expr" />
+ <data id="short_expr" expr="0" />
+ <data id="res" />
+ </datamodel>
+ <state id="wrapper" initial="on">
+ <state id="on" initial="ready">
+ <onentry>
+ <send event="DISPLAY.UPDATE" />
+ </onentry>
+ <state id="ready" initial="begin">
+ <state id="begin">
+ <transition event="OPER.MINUS" target="negated1" />
+ <onentry>
+ <send event="DISPLAY.UPDATE" />
+ </onentry>
+ </state>
+ <state id="result">
+ </state>
+ <transition event="OPER" target="opEntered" />
+ <transition event="DIGIT.0" target="zero1">
+ <assign location="short_expr" expr="''" />
+ </transition>
+ <transition event="DIGIT" target="int1">
+ <assign location="short_expr" expr="''" />
+ </transition>
+ <transition event="POINT" target="frac1">
+ <assign location="short_expr" expr="''" />
+ </transition>
+ </state>
+ <state id="negated1">
+ <onentry>
+ <assign location="short_expr" expr="'-'" />
+ <send event="DISPLAY.UPDATE" />
+ </onentry>
+ <transition event="DIGIT.0" target="zero1" />
+ <transition event="DIGIT" target="int1" />
+ <transition event="POINT" target="frac1" />
+ </state>
+ <state id="operand1">
+ <state id="zero1">
+ <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int1" />
+ <transition event="POINT" target="frac1" />
+ </state>
+ <state id="int1">
+ <transition event="POINT" target="frac1" />
+ <transition event="DIGIT">
+ <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" />
+ <send event="DISPLAY.UPDATE" />
+ </transition>
+ <onentry>
+ <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" />
+ <send event="DISPLAY.UPDATE" />
+ </onentry>
+ </state>
+ <state id="frac1">
+ <onentry>
+ <assign location="short_expr" expr="short_expr+'.'" />
+ <send event="DISPLAY.UPDATE" />
+ </onentry>
+ <transition event="DIGIT">
+ <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" />
+ <send event="DISPLAY.UPDATE" />
+ </transition>
+ </state>
+ <transition event="OPER" target="opEntered" />
+ </state>
+ <state id="opEntered">
+ <transition event="OPER.MINUS" target="negated2" />
+ <transition event="POINT" target="frac2" />
+ <transition event="DIGIT.0" target="zero2" />
+ <transition event="DIGIT" target="int2" />
+ <onentry>
+ <raise event="CALC.SUB" />
+ <send target="_internal" event="OP.INSERT">
+ <param name="operator" expr="_event.name" />
+ </send>
+ </onentry>
+ </state>
+ <state id="negated2">
+ <onentry>
+ <assign location="short_expr" expr="'-'" />
+ <send event="DISPLAY.UPDATE" />
+ </onentry>
+ <transition event="DIGIT.0" target="zero2" />
+ <transition event="DIGIT" target="int2" />
+ <transition event="POINT" target="frac2" />
+ </state>
+ <state id="operand2">
+ <state id="zero2">
+ <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int2" />
+ <transition event="POINT" target="frac2" />
+ </state>
+ <state id="int2">
+ <transition event="DIGIT">
+ <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" />
+ <send event="DISPLAY.UPDATE" />
+ </transition>
+ <onentry>
+ <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" />
+ <send event="DISPLAY.UPDATE" />
+ </onentry>
+ <transition event="POINT" target="frac2" />
+ </state>
+ <state id="frac2">
+ <onentry>
+ <assign location="short_expr" expr="short_expr +'.'" />
+ <send event="DISPLAY.UPDATE" />
+ </onentry>
+ <transition event="DIGIT">
+ <assign location="short_expr" expr="short_expr +_event.name.substr(_event.name.lastIndexOf('.')+1)" />
+ <send event="DISPLAY.UPDATE" />
+ </transition>
+ </state>
+ <transition event="OPER" target="opEntered">
+ <raise event="CALC.SUB" />
+ <raise event="OP.INSERT" />
+ </transition>
+ <transition event="EQUALS" target="result">
+ <raise event="CALC.SUB" />
+ <raise event="CALC.DO" />
+ </transition>
+ </state>
+ <transition event="C" target="on" />
+ </state>
+ <transition event="CALC.DO">
+ <assign location="short_expr" expr="''+ res" />
+ <assign location="long_expr" expr="''" />
+ <assign location="res" expr="0" />
+ </transition>
+ <transition event="CALC.SUB">
+ <if cond="short_expr!=''">
+ <assign location="long_expr" expr="long_expr+'('+short_expr+')'" />
+ </if>
+ <assign location="res" expr="eval(long_expr)" />
+ <assign location="short_expr" expr="''" />
+ <send event="DISPLAY.UPDATE" />
+ </transition>
+ <transition event="DISPLAY.UPDATE">
+ <!--log level="0" label="'result'" expr=".short_expr==''?res:short_expr" / -->
+ </transition>
+ <transition event="OP.INSERT">
+ <log level="0" expr="_event.data[0]" />
+ <if cond="_event.data[0] == 'OPER.PLUS'">
+ <assign location="long_expr" expr="long_expr+'+'" />
+ <elseif cond="_event.data[0]=='OPER.MINUS'" />
+ <assign location="long_expr" expr="long_expr+'-'" />
+ <elseif cond="_event.data[0]=='OPER.STAR'" />
+ <assign location="long_expr" expr="long_expr+'*'" />
+ <elseif cond="_event.data[0]=='OPER.DIV'" />
+ <assign location="long_expr" expr="long_expr+'/'" />
+ </if>
+ </transition>
+ </state>
+</scxml>