| /* $Id: json.cpp 4704 2014-01-16 05:30:46Z ming $ */ |
| /* |
| * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com) |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| #include <pjsua2/json.hpp> |
| #include <pjlib-util/errno.h> |
| #include <pj/file_io.h> |
| #include "util.hpp" |
| |
| #define THIS_FILE "json.cpp" |
| |
| using namespace pj; |
| using namespace std; |
| |
| /* Json node operations */ |
| static bool jsonNode_hasUnread(const ContainerNode*); |
| static string jsonNode_unreadName(const ContainerNode*n) |
| throw(Error); |
| static float jsonNode_readNumber(const ContainerNode*, |
| const string&) |
| throw(Error); |
| static bool jsonNode_readBool(const ContainerNode*, |
| const string&) |
| throw(Error); |
| static string jsonNode_readString(const ContainerNode*, |
| const string&) |
| throw(Error); |
| static StringVector jsonNode_readStringVector(const ContainerNode*, |
| const string&) |
| throw(Error); |
| static ContainerNode jsonNode_readContainer(const ContainerNode*, |
| const string &) |
| throw(Error); |
| static ContainerNode jsonNode_readArray(const ContainerNode*, |
| const string &) |
| throw(Error); |
| static void jsonNode_writeNumber(ContainerNode*, |
| const string &name, |
| float num) |
| throw(Error); |
| static void jsonNode_writeBool(ContainerNode*, |
| const string &name, |
| bool value) |
| throw(Error); |
| static void jsonNode_writeString(ContainerNode*, |
| const string &name, |
| const string &value) |
| throw(Error); |
| static void jsonNode_writeStringVector(ContainerNode*, |
| const string &name, |
| const StringVector &value) |
| throw(Error); |
| static ContainerNode jsonNode_writeNewContainer(ContainerNode*, |
| const string &name) |
| throw(Error); |
| static ContainerNode jsonNode_writeNewArray(ContainerNode*, |
| const string &name) |
| throw(Error); |
| |
| static container_node_op json_op = |
| { |
| &jsonNode_hasUnread, |
| &jsonNode_unreadName, |
| &jsonNode_readNumber, |
| &jsonNode_readBool, |
| &jsonNode_readString, |
| &jsonNode_readStringVector, |
| &jsonNode_readContainer, |
| &jsonNode_readArray, |
| &jsonNode_writeNumber, |
| &jsonNode_writeBool, |
| &jsonNode_writeString, |
| &jsonNode_writeStringVector, |
| &jsonNode_writeNewContainer, |
| &jsonNode_writeNewArray |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| JsonDocument::JsonDocument() |
| : root(NULL) |
| { |
| pj_caching_pool_init(&cp, NULL, 0); |
| pool = pj_pool_create(&cp.factory, "jsondoc", 512, 512, NULL); |
| if (!pool) |
| PJSUA2_RAISE_ERROR(PJ_ENOMEM); |
| } |
| |
| JsonDocument::~JsonDocument() |
| { |
| if (pool) |
| pj_pool_release(pool); |
| pj_caching_pool_destroy(&cp); |
| } |
| |
| void JsonDocument::initRoot() const |
| { |
| rootNode.op = &json_op; |
| rootNode.data.doc = (void*)this; |
| rootNode.data.data1 = (void*)root; |
| rootNode.data.data2 = root->value.children.next; |
| } |
| |
| void JsonDocument::loadFile(const string &filename) throw(Error) |
| { |
| if (root) |
| PJSUA2_RAISE_ERROR3(PJ_EINVALIDOP, "JsonDocument.loadString()", |
| "Document already initialized"); |
| |
| if (!pj_file_exists(filename.c_str())) |
| PJSUA2_RAISE_ERROR(PJ_ENOTFOUND); |
| |
| pj_ssize_t size = (pj_ssize_t)pj_file_size(filename.c_str()); |
| pj_status_t status; |
| |
| char *buffer = (char*)pj_pool_alloc(pool, size+1); |
| pj_oshandle_t fd = 0; |
| unsigned parse_size; |
| char err_msg[120]; |
| pj_json_err_info err_info; |
| |
| err_msg[0] = '\0'; |
| |
| status = pj_file_open(pool, filename.c_str(), PJ_O_RDONLY, &fd); |
| if (status != PJ_SUCCESS) |
| goto on_error; |
| |
| status = pj_file_read(fd, buffer, &size); |
| if (status != PJ_SUCCESS) |
| goto on_error; |
| |
| pj_file_close(fd); |
| fd = NULL; |
| |
| if (size <= 0) { |
| status = PJ_EEOF; |
| goto on_error; |
| } |
| |
| parse_size = (unsigned)size; |
| root = pj_json_parse(pool, buffer, &parse_size, &err_info); |
| if (root == NULL) { |
| pj_ansi_snprintf(err_msg, sizeof(err_msg), |
| "JSON parsing failed: syntax error in file '%s' at " |
| "line %d column %d", |
| filename.c_str(), err_info.line, err_info.col); |
| PJ_LOG(1,(THIS_FILE, err_msg)); |
| status = PJLIB_UTIL_EINJSON; |
| goto on_error; |
| } |
| |
| initRoot(); |
| return; |
| |
| on_error: |
| if (fd) |
| pj_file_close(fd); |
| if (err_msg[0]) |
| PJSUA2_RAISE_ERROR3(status, "loadFile()", err_msg); |
| else |
| PJSUA2_RAISE_ERROR(status); |
| } |
| |
| void JsonDocument::loadString(const string &input) throw(Error) |
| { |
| if (root) |
| PJSUA2_RAISE_ERROR3(PJ_EINVALIDOP, "JsonDocument.loadString()", |
| "Document already initialized"); |
| |
| unsigned size = input.size(); |
| char *buffer = (char*)pj_pool_alloc(pool, size+1); |
| unsigned parse_size = (unsigned)size; |
| pj_json_err_info err_info; |
| |
| pj_memcpy(buffer, input.c_str(), size); |
| |
| root = pj_json_parse(pool, buffer, &parse_size, &err_info); |
| if (root == NULL) { |
| char err_msg[80]; |
| |
| pj_ansi_snprintf(err_msg, sizeof(err_msg), |
| "JSON parsing failed at line %d column %d", |
| err_info.line, err_info.col); |
| PJ_LOG(1,(THIS_FILE, err_msg)); |
| PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, "loadString()", err_msg); |
| } |
| initRoot(); |
| } |
| |
| struct save_file_data |
| { |
| pj_oshandle_t fd; |
| }; |
| |
| static pj_status_t json_file_writer(const char *s, |
| unsigned size, |
| void *user_data) |
| { |
| save_file_data *sd = (save_file_data*)user_data; |
| pj_ssize_t ssize = (pj_ssize_t)size; |
| return pj_file_write(sd->fd, s, &ssize); |
| } |
| |
| void JsonDocument::saveFile(const string &filename) throw(Error) |
| { |
| struct save_file_data sd; |
| pj_status_t status; |
| |
| /* Make sure root container has been created */ |
| getRootContainer(); |
| |
| status = pj_file_open(pool, filename.c_str(), PJ_O_WRONLY, &sd.fd); |
| if (status != PJ_SUCCESS) |
| PJSUA2_RAISE_ERROR(status); |
| |
| status = pj_json_writef(root, &json_file_writer, &sd.fd); |
| pj_file_close(sd.fd); |
| |
| if (status != PJ_SUCCESS) |
| PJSUA2_RAISE_ERROR(status); |
| } |
| |
| struct save_string_data |
| { |
| string output; |
| }; |
| |
| static pj_status_t json_string_writer(const char *s, |
| unsigned size, |
| void *user_data) |
| { |
| save_string_data *sd = (save_string_data*)user_data; |
| sd->output.append(s, size); |
| return PJ_SUCCESS; |
| } |
| |
| string JsonDocument::saveString() throw(Error) |
| { |
| struct save_string_data sd; |
| pj_status_t status; |
| |
| /* Make sure root container has been created */ |
| getRootContainer(); |
| |
| status = pj_json_writef(root, &json_string_writer, &sd); |
| if (status != PJ_SUCCESS) |
| PJSUA2_RAISE_ERROR(status); |
| |
| return sd.output; |
| } |
| |
| ContainerNode & JsonDocument::getRootContainer() const |
| { |
| if (!root) { |
| root = allocElement(); |
| pj_json_elem_obj(root, NULL); |
| initRoot(); |
| } |
| |
| return rootNode; |
| } |
| |
| pj_json_elem* JsonDocument::allocElement() const |
| { |
| return (pj_json_elem*)pj_pool_alloc(pool, sizeof(pj_json_elem)); |
| } |
| |
| pj_pool_t *JsonDocument::getPool() |
| { |
| return pool; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| struct json_node_data |
| { |
| JsonDocument *doc; |
| pj_json_elem *jnode; |
| pj_json_elem *childPtr; |
| }; |
| |
| static bool jsonNode_hasUnread(const ContainerNode *node) |
| { |
| json_node_data *jdat = (json_node_data*)&node->data; |
| return jdat->childPtr != (pj_json_elem*)&jdat->jnode->value.children; |
| } |
| |
| static void json_verify(struct json_node_data *jdat, |
| const char *op, |
| const string &name, |
| pj_json_val_type type) |
| { |
| if (jdat->childPtr == (pj_json_elem*)&jdat->jnode->value.children) |
| PJSUA2_RAISE_ERROR3(PJ_EEOF, op, "No unread element"); |
| |
| /* If name is specified, then check if the names match, except |
| * when the node name itself is empty and the parent node is |
| * an array, then ignore the checking (JSON doesn't allow array |
| * elements to have name). |
| */ |
| if (name.size() && name.compare(0, name.size(), |
| jdat->childPtr->name.ptr, |
| jdat->childPtr->name.slen) && |
| jdat->childPtr->name.slen && |
| jdat->jnode->type != PJ_JSON_VAL_ARRAY) |
| { |
| char err_msg[80]; |
| pj_ansi_snprintf(err_msg, sizeof(err_msg), |
| "Name mismatch: expecting '%s' got '%.*s'", |
| name.c_str(), (int)jdat->childPtr->name.slen, |
| jdat->childPtr->name.ptr); |
| PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, op, err_msg); |
| } |
| |
| if (type != PJ_JSON_VAL_NULL && jdat->childPtr->type != type) { |
| char err_msg[80]; |
| pj_ansi_snprintf(err_msg, sizeof(err_msg), |
| "Type mismatch: expecting %d got %d", |
| type, jdat->childPtr->type); |
| PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, op, err_msg); |
| } |
| } |
| |
| static string jsonNode_unreadName(const ContainerNode *node) |
| throw(Error) |
| { |
| json_node_data *jdat = (json_node_data*)&node->data; |
| json_verify(jdat, "unreadName()", "", PJ_JSON_VAL_NULL); |
| return pj2Str(jdat->childPtr->name); |
| } |
| |
| static float jsonNode_readNumber(const ContainerNode *node, |
| const string &name) |
| throw(Error) |
| { |
| json_node_data *jdat = (json_node_data*)&node->data; |
| json_verify(jdat, "readNumber()", name, PJ_JSON_VAL_NUMBER); |
| jdat->childPtr = jdat->childPtr->next; |
| return jdat->childPtr->prev->value.num; |
| } |
| |
| static bool jsonNode_readBool(const ContainerNode *node, |
| const string &name) |
| throw(Error) |
| { |
| json_node_data *jdat = (json_node_data*)&node->data; |
| json_verify(jdat, "readBool()", name, PJ_JSON_VAL_BOOL); |
| jdat->childPtr = jdat->childPtr->next; |
| return PJ2BOOL(jdat->childPtr->prev->value.is_true); |
| } |
| |
| static string jsonNode_readString(const ContainerNode *node, |
| const string &name) |
| throw(Error) |
| { |
| json_node_data *jdat = (json_node_data*)&node->data; |
| json_verify(jdat, "readString()", name, PJ_JSON_VAL_STRING); |
| jdat->childPtr = jdat->childPtr->next; |
| return pj2Str(jdat->childPtr->prev->value.str); |
| } |
| |
| static StringVector jsonNode_readStringVector(const ContainerNode *node, |
| const string &name) |
| throw(Error) |
| { |
| json_node_data *jdat = (json_node_data*)&node->data; |
| json_verify(jdat, "readStringVector()", name, PJ_JSON_VAL_ARRAY); |
| |
| StringVector result; |
| pj_json_elem *child = jdat->childPtr->value.children.next; |
| while (child != (pj_json_elem*)&jdat->childPtr->value.children) { |
| if (child->type != PJ_JSON_VAL_STRING) { |
| char err_msg[80]; |
| pj_ansi_snprintf(err_msg, sizeof(err_msg), |
| "Elements not string but type %d", |
| child->type); |
| PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, "readStringVector()", |
| err_msg); |
| } |
| result.push_back(pj2Str(child->value.str)); |
| child = child->next; |
| } |
| |
| jdat->childPtr = jdat->childPtr->next; |
| return result; |
| } |
| |
| static ContainerNode jsonNode_readContainer(const ContainerNode *node, |
| const string &name) |
| throw(Error) |
| { |
| json_node_data *jdat = (json_node_data*)&node->data; |
| json_verify(jdat, "readContainer()", name, PJ_JSON_VAL_OBJ); |
| |
| ContainerNode json_node; |
| |
| json_node.op = &json_op; |
| json_node.data.doc = (void*)jdat->doc; |
| json_node.data.data1 = (void*)jdat->childPtr; |
| json_node.data.data2 = (void*)jdat->childPtr->value.children.next; |
| |
| jdat->childPtr = jdat->childPtr->next; |
| return json_node; |
| } |
| |
| static ContainerNode jsonNode_readArray(const ContainerNode *node, |
| const string &name) |
| throw(Error) |
| { |
| json_node_data *jdat = (json_node_data*)&node->data; |
| json_verify(jdat, "readArray()", name, PJ_JSON_VAL_ARRAY); |
| |
| ContainerNode json_node; |
| |
| json_node.op = &json_op; |
| json_node.data.doc = (void*)jdat->doc; |
| json_node.data.data1 = (void*)jdat->childPtr; |
| json_node.data.data2 = (void*)jdat->childPtr->value.children.next; |
| |
| jdat->childPtr = jdat->childPtr->next; |
| return json_node; |
| } |
| |
| static pj_str_t alloc_name(JsonDocument *doc, const string &name) |
| { |
| pj_str_t new_name; |
| pj_strdup2(doc->getPool(), &new_name, name.c_str()); |
| return new_name; |
| } |
| |
| static void jsonNode_writeNumber(ContainerNode *node, |
| const string &name, |
| float num) |
| throw(Error) |
| { |
| json_node_data *jdat = (json_node_data*)&node->data; |
| pj_json_elem *el = jdat->doc->allocElement(); |
| pj_str_t nm = alloc_name(jdat->doc, name); |
| pj_json_elem_number(el, &nm, num); |
| pj_json_elem_add(jdat->jnode, el); |
| } |
| |
| static void jsonNode_writeBool(ContainerNode *node, |
| const string &name, |
| bool value) |
| throw(Error) |
| { |
| json_node_data *jdat = (json_node_data*)&node->data; |
| pj_json_elem *el = jdat->doc->allocElement(); |
| pj_str_t nm = alloc_name(jdat->doc, name); |
| pj_json_elem_bool(el, &nm, value); |
| pj_json_elem_add(jdat->jnode, el); |
| } |
| |
| static void jsonNode_writeString(ContainerNode *node, |
| const string &name, |
| const string &value) |
| throw(Error) |
| { |
| json_node_data *jdat = (json_node_data*)&node->data; |
| pj_json_elem *el = jdat->doc->allocElement(); |
| pj_str_t nm = alloc_name(jdat->doc, name); |
| pj_str_t new_val; |
| pj_strdup2(jdat->doc->getPool(), &new_val, value.c_str()); |
| pj_json_elem_string(el, &nm, &new_val); |
| |
| pj_json_elem_add(jdat->jnode, el); |
| } |
| |
| static void jsonNode_writeStringVector(ContainerNode *node, |
| const string &name, |
| const StringVector &value) |
| throw(Error) |
| { |
| json_node_data *jdat = (json_node_data*)&node->data; |
| pj_json_elem *el = jdat->doc->allocElement(); |
| pj_str_t nm = alloc_name(jdat->doc, name); |
| |
| pj_json_elem_array(el, &nm); |
| for (unsigned i=0; i<value.size(); ++i) { |
| pj_str_t new_val; |
| |
| pj_strdup2(jdat->doc->getPool(), &new_val, value[i].c_str()); |
| pj_json_elem *child = jdat->doc->allocElement(); |
| pj_json_elem_string(child, NULL, &new_val); |
| pj_json_elem_add(el, child); |
| } |
| |
| pj_json_elem_add(jdat->jnode, el); |
| } |
| |
| static ContainerNode jsonNode_writeNewContainer(ContainerNode *node, |
| const string &name) |
| throw(Error) |
| { |
| json_node_data *jdat = (json_node_data*)&node->data; |
| pj_json_elem *el = jdat->doc->allocElement(); |
| pj_str_t nm = alloc_name(jdat->doc, name); |
| |
| pj_json_elem_obj(el, &nm); |
| pj_json_elem_add(jdat->jnode, el); |
| |
| ContainerNode json_node; |
| |
| json_node.op = &json_op; |
| json_node.data.doc = (void*)jdat->doc; |
| json_node.data.data1 = (void*)el; |
| json_node.data.data2 = (void*)el->value.children.next; |
| |
| return json_node; |
| } |
| |
| static ContainerNode jsonNode_writeNewArray(ContainerNode *node, |
| const string &name) |
| throw(Error) |
| { |
| json_node_data *jdat = (json_node_data*)&node->data; |
| pj_json_elem *el = jdat->doc->allocElement(); |
| pj_str_t nm = alloc_name(jdat->doc, name); |
| |
| pj_json_elem_array(el, &nm); |
| pj_json_elem_add(jdat->jnode, el); |
| |
| ContainerNode json_node; |
| |
| json_node.op = &json_op; |
| json_node.data.doc = (void*)jdat->doc; |
| json_node.data.data1 = (void*)el; |
| json_node.data.data2 = (void*)el->value.children.next; |
| |
| return json_node; |
| } |