summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68>2009-10-03 21:50:50 +0000
committerjsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68>2009-10-03 21:50:50 +0000
commita9f824759fae580662e0b6f816d0d4beb9c3c385 (patch)
tree8f833b01b4199e7d6734a6242058c7f56972763e
parent980d94493097ba3f93d1f4390fe90fca24b68444 (diff)
Strawman implementation of a JSON data binding.
git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@821429 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--cpp/sca/configure.ac12
-rw-r--r--cpp/sca/etc/git-exclude1
-rw-r--r--cpp/sca/modules/Makefile.am3
-rw-r--r--cpp/sca/modules/json/Makefile.am24
-rw-r--r--cpp/sca/modules/json/json-test.cpp76
-rw-r--r--cpp/sca/modules/json/json.hpp349
6 files changed, 462 insertions, 3 deletions
diff --git a/cpp/sca/configure.ac b/cpp/sca/configure.ac
index 457c53d09d..9aec70259b 100644
--- a/cpp/sca/configure.ac
+++ b/cpp/sca/configure.ac
@@ -85,12 +85,21 @@ LIBXML2_INCLUDE=`echo "$LIBXML2_INCLUDE"`
if test "x$LIBXML2_INCLUDE" = "x"; then
AC_SUBST([LIBXML2_INCLUDE], ["/usr/include/libxml2"])
fi
-
LIBXML2_LIB=`echo "$LIBXML2_LIB"`
if test "x$LIBXML2_LIB" = "x"; then
AC_SUBST([LIBXML2_LIB], ["/usr/lib"])
fi
+# Configure LIBMOZJS_INCLUDE and LIBMOZJS_LIB
+LIBMOZJS_INCLUDE=`echo "$LIBMOZJS_INCLUDE"`
+if test "x$LIBMOZJS_INCLUDE" = "x"; then
+ AC_SUBST([LIBMOZJS_INCLUDE], ["/usr/include/xulrunner-1.9.1.3/unstable"])
+fi
+LIBMOZJS_LIB=`echo "$LIBMOZJS_LIB"`
+if test "x$LIBMOZJS_LIB" = "x"; then
+ AC_SUBST([LIBMOZJS_LIB], ["/usr/lib/xulrunner-1.9.1.3"])
+fi
+
# Configure GCC C++ compile options
AC_SUBST([CXXFLAGS], ["$(CXXFLAGS) -D_DEBUG -O0 -g3 -Wall -std=c++0x -fmessage-length=0"])
@@ -258,6 +267,7 @@ AC_CONFIG_FILES([Makefile
kernel/Makefile
modules/Makefile
modules/eval/Makefile
+ modules/json/Makefile
test/Makefile
test/store-object/Makefile
test/store-function/Makefile
diff --git a/cpp/sca/etc/git-exclude b/cpp/sca/etc/git-exclude
index 0350dd230f..048c6979a1 100644
--- a/cpp/sca/etc/git-exclude
+++ b/cpp/sca/etc/git-exclude
@@ -62,6 +62,7 @@ calculator_client
kernel-test
xml-test
eval-test
+json-test
store-function-test
store-object-test
store-script-test
diff --git a/cpp/sca/modules/Makefile.am b/cpp/sca/modules/Makefile.am
index cfc3245f7b..0c9ccc4fbc 100644
--- a/cpp/sca/modules/Makefile.am
+++ b/cpp/sca/modules/Makefile.am
@@ -15,5 +15,4 @@
# specific language governing permissions and limitations
# under the License.
-EVAL_MODULE = eval
-SUBDIRS = ${EVAL_MODULE}
+SUBDIRS = eval json
diff --git a/cpp/sca/modules/json/Makefile.am b/cpp/sca/modules/json/Makefile.am
new file mode 100644
index 0000000000..d14b5165e6
--- /dev/null
+++ b/cpp/sca/modules/json/Makefile.am
@@ -0,0 +1,24 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+noinst_PROGRAMS = json-test
+
+INCLUDES = -I. -I$(top_builddir)/kernel -I${LIBXML2_INCLUDE} -I${LIBMOZJS_INCLUDE}
+
+json_test_SOURCES = json-test.cpp
+json_test_LDADD = -lpthread -L${LIBXML2_LIB} -lxml2 -L${LIBMOZJS_LIB} -lmozjs
+
diff --git a/cpp/sca/modules/json/json-test.cpp b/cpp/sca/modules/json/json-test.cpp
new file mode 100644
index 0000000000..42cdb1d643
--- /dev/null
+++ b/cpp/sca/modules/json/json-test.cpp
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* $Rev$ $Date$ */
+
+/**
+ * Test JSON read/write functions.
+ */
+
+#include <assert.h>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include "json.hpp"
+
+namespace tuscany {
+
+bool testJSEval() {
+ JSONContext cx;
+ std::string script("(function testJSON(n){ return JSON.parse(JSON.stringify(n)) })(5)");
+ jsval rval;
+ assert(JS_EvaluateScript(cx, cx.getGlobal(), script.c_str(), script.length(), "testJSON.js", 1, &rval));
+ const std::string r(JS_GetStringBytes(JS_ValueToString(cx, rval)));
+ assert(r == "5");
+ return true;
+}
+
+bool testJSON() {
+ JSONContext cx;
+
+ list<value> phones = makeList<value> (std::string("408-1234"), std::string("650-1234"));
+ list<value> l = makeList<value> (makeList<value> ("phones", phones), makeList<value> ("lastName", std::string("test\ttab")), makeList<value> ("firstName", std::string("test1")));
+ print(l, std::cout);
+ std::cout << std::endl;
+
+ std::ostringstream sos;
+ writeJSON(cx, l, sos);
+ std::cout << sos.str() << std::endl;
+
+ std::istringstream is(sos.str());
+ list<value> r = readJSON(cx, is);
+ print(r, std::cout);
+ std::cout << std::endl;
+ assert(r == l);
+
+ return true;
+}
+
+}
+
+int main() {
+ std::cout << "Testing..." << std::endl;
+
+ tuscany::testJSEval();
+ tuscany::testJSON();
+
+ std::cout << "OK" << std::endl;
+
+ return 0;
+}
diff --git a/cpp/sca/modules/json/json.hpp b/cpp/sca/modules/json/json.hpp
new file mode 100644
index 0000000000..16e3ce0320
--- /dev/null
+++ b/cpp/sca/modules/json/json.hpp
@@ -0,0 +1,349 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* $Rev$ $Date$ */
+
+#ifndef tuscany_eval_driver_hpp
+#define tuscany_eval_driver_hpp
+
+/**
+ * JSON read/write functions.
+ */
+
+#include <assert.h>
+#define XP_UNIX
+#include <jsapi.h>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include "list.hpp"
+#include "value.hpp"
+
+namespace tuscany {
+
+/**
+ * Report JSON errors.
+ */
+void reportError(JSContext *cx, const char *message, JSErrorReport *report) {
+ std::cerr << (const char*)(report->filename? report->filename : "<no filename>") << ":"
+ << (unsigned int)report->lineno << ":" << message << std::endl;
+}
+
+/**
+ * Encapsulates a JavaScript runtime. Can be shared by multiple threads in
+ * a process.
+ */
+class JSONRuntime {
+public:
+ JSONRuntime() {
+ // Create JS runtime
+ rt = JS_NewRuntime(8L * 1024L * 1024L);
+ if(rt == NULL)
+ cleanup();
+ }
+
+ ~JSONRuntime() {
+ }
+
+ operator JSRuntime*() const {
+ return rt;
+ }
+private:
+ bool cleanup() {
+ if(rt != NULL) {
+ JS_DestroyRuntime(rt);
+ rt = NULL;
+ }
+ JS_ShutDown();
+ return true;
+ }
+
+ JSRuntime* rt;
+};
+
+/**
+ * Global JavaScript runtime instance.
+ */
+JSONRuntime jsRuntime;
+
+JSClass jsGlobalClass = { "global", JSCLASS_GLOBAL_FLAGS, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
+ JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS};
+
+
+/**
+ * Represents a JavaScript context. Create one per thread.
+ */
+class JSONContext {
+public:
+ JSONContext() {
+ // Create JS context
+ cx = JS_NewContext(jsRuntime, 8192);
+ if(cx == NULL)
+ return;
+ JS_SetOptions(cx, JSOPTION_VAROBJFIX);
+ JS_SetVersion(cx, JSVERSION_DEFAULT);
+ JS_SetErrorReporter(cx, reportError);
+
+ // Create global JS object
+ global = JS_NewObject(cx, &jsGlobalClass, NULL, NULL);
+ if(global == NULL) {
+ cleanup();
+ return;
+ }
+
+ // Populate global object with the standard globals, like Object and Array
+ if(!JS_InitStandardClasses(cx, global)) {
+ cleanup();
+ return;
+ }
+ }
+
+ ~JSONContext() {
+ cleanup();
+ }
+
+ operator JSContext*() const {
+ return cx;
+ }
+
+ JSObject* getGlobal() const {
+ return global;
+ }
+
+private:
+ bool cleanup() {
+ if(cx != NULL) {
+ JS_DestroyContext(cx);
+ cx = NULL;
+ }
+ return true;
+ }
+
+ JSContext* cx;
+ JSObject* global;
+};
+
+/**
+ * Converts JS properties to Tuscany values.
+ */
+const list<value> jsPropertiesToValues(const JSONContext& cx, const list<value>& propertiesSoFar, JSObject* o,
+ JSObject* i) {
+
+ const value jsValToValue(const JSONContext& cx, const jsval& jsv);
+
+ jsid id;
+ if(!JS_NextProperty(cx, i, &id) || id == JSVAL_VOID)
+ return propertiesSoFar;
+ jsval jsv;
+ if(!JS_GetPropertyById(cx, o, id, &jsv))
+ return propertiesSoFar;
+ const value val = jsValToValue(cx, jsv);
+ jsval idv;
+ JS_IdToValue(cx, id, &idv);
+ if(JSVAL_IS_STRING(idv))
+ return jsPropertiesToValues(cx, cons<value> (makeList<value> (JS_GetStringBytes(JSVAL_TO_STRING(idv)), val),
+ propertiesSoFar), o, i);
+ return jsPropertiesToValues(cx, cons(val, propertiesSoFar), o, i);
+}
+
+/**
+ * Converts a JS value to a Tuscany value.
+ */
+const value jsValToValue(const JSONContext& cx, const jsval& jsv) {
+ switch(JS_TypeOfValue(cx, jsv)) {
+ case JSTYPE_STRING: {
+ return value(std::string(JS_GetStringBytes(JSVAL_TO_STRING(jsv))));
+ }
+ case JSTYPE_BOOLEAN: {
+ return value((bool)JSVAL_TO_BOOLEAN(jsv));
+ }
+ case JSTYPE_NUMBER: {
+ jsdouble* jsd = JSVAL_TO_DOUBLE(jsv);
+ return value((double)*jsd);
+ }
+ case JSTYPE_OBJECT: {
+ JSObject* o = JSVAL_TO_OBJECT(jsv);
+ JSObject* i = JS_NewPropertyIterator(cx, o);
+ if(i == NULL)
+ return value(list<value> ());
+ return jsPropertiesToValues(cx, list<value> (), o, i);
+ }
+ default: {
+ return value();
+ }
+ }
+}
+
+/**
+ * Reads characters from a JSON input stream.
+ */
+JSString* readCallback(const JSONContext& cx, std::istream& is) {
+ char buffer[1024];
+ if(is.eof())
+ return NULL;
+ is.read(buffer, 1024);
+ const int n = is.gcount();
+ if(n <= 0)
+ return NULL;
+ return JS_NewStringCopyN(cx, buffer, n);
+}
+
+/**
+ * Consumes a JSON document and populates a JS object from it.
+ */
+bool consumeJSON(const JSONContext& cx, JSONParser* parser, std::istream& is) {
+ JSString* jstr = readCallback(cx, is);
+ if(jstr == NULL)
+ return true;
+ if(!JS_ConsumeJSONText(cx, parser, JS_GetStringChars(jstr), JS_GetStringLength(jstr)))
+ return false;
+ return consumeJSON(cx, parser, is);
+}
+
+/**
+ * Read a JSON document from an input stream.
+ */
+const list<value> readJSON(const JSONContext& cx, std::istream& is) {
+ jsval val;
+ JSONParser* parser = JS_BeginJSONParse(cx, &val);
+ if(parser == NULL)
+ return list<value> ();
+
+ bool ok = consumeJSON(cx, parser, is);
+
+ if(!JS_FinishJSONParse(cx, parser, JSVAL_NULL))
+ return list<value> ();
+ if(!ok)
+ return list<value> ();
+
+ return jsValToValue(cx, val);
+}
+
+/**
+ * Returns true if a list represents a JS array.
+ */
+const bool isJSArray(const list<value>& l) {
+ if(l == list<value> ())
+ return false;
+ value v = car(l);
+ if(isList(v)) {
+ const list<value> p = v;
+ if(isSymbol(car(p))) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Converts a list of values to JS array elements.
+ */
+JSObject* valuesToJSElements(const JSONContext& cx, JSObject* a, const list<value>& l, int i) {
+ const jsval valueToJSVal(const JSONContext& cx, const value& val);
+
+ if (l == list<value>())
+ return a;
+ jsval pv = valueToJSVal(cx, car(l));
+ JS_SetElement(cx, a, i, &pv);
+ return valuesToJSElements(cx, a, cdr(l), ++i);
+}
+
+/**
+ * Converts a list of values to JS properties.
+ */
+JSObject* valuesToJSProperties(const JSONContext& cx, JSObject* o, const list<value>& l) {
+ const jsval valueToJSVal(const JSONContext& cx, const value& val);
+
+ if(l == list<value> ())
+ return o;
+ const list<value> p = car(l);
+ jsval pv = valueToJSVal(cx, cadr(p));
+ JS_SetProperty(cx, o, ((std::string)car(p)).c_str(), &pv);
+ return valuesToJSProperties(cx, o, cdr(l));
+}
+
+/**
+ * Converts a Tuscany value to a JS value.
+ */
+const jsval valueToJSVal(const JSONContext& cx, const value& val) {
+ switch(type(val)) {
+ case value::String: {
+ return STRING_TO_JSVAL(JS_NewStringCopyZ(cx, ((std::string)val).c_str()));
+ }
+ case value::Boolean: {
+ return BOOLEAN_TO_JSVAL((bool)val);
+ }
+ case value::Number: {
+ jsdouble d = (double)val;
+ return DOUBLE_TO_JSVAL(&d);
+ }
+ case value::List: {
+ if (isJSArray(val)) {
+ return OBJECT_TO_JSVAL(valuesToJSElements(cx, JS_NewArrayObject(cx, 0, NULL), val, 0));
+ }
+ return OBJECT_TO_JSVAL(valuesToJSProperties(cx, JS_NewObject(cx, NULL, NULL, NULL), val));
+ }
+ default: {
+ return JSVAL_VOID;
+ }
+ }
+}
+
+/**
+ * Context passed to JSON write callback function.
+ */
+class JSONWriteContext {
+public:
+ JSONWriteContext(const JSONContext& cx, std::ostream& os) :
+ os(os), cx(cx) {
+ }
+
+private:
+ std::ostream& os;
+ const JSONContext& cx;
+
+ friend JSBool writeCallback(const jschar *buf, uint32 len, void *data);
+};
+
+/**
+ * Called by JS_Stringify to write JSON document.
+ */
+JSBool writeCallback(const jschar *buf, uint32 len, void *data) {
+ JSONWriteContext& cx = *(static_cast<JSONWriteContext*> (data));
+ JSString* jstr = JS_NewUCStringCopyN(cx.cx, buf, len);
+ cx.os.write(JS_GetStringBytes(jstr), JS_GetStringLength(jstr));
+ if(cx.os.fail() || cx.os.bad())
+ return JS_FALSE;
+ return JS_TRUE;
+}
+
+/**
+ * Write a JSON document to an output stream.
+ */
+const bool writeJSON(const JSONContext& cx, const list<value>& l, std::ostream& os) {
+ jsval val = valueToJSVal(cx, l);
+ JSONWriteContext wcx(cx, os);
+ if(!JS_Stringify(cx, &val, NULL, JSVAL_NULL, writeCallback, &wcx))
+ return false;
+ return true;
+}
+
+}
+
+#endif