blob: 12bef12af4b4e876031780ccd5295044a0164aa8 [file] [log] [blame]
/* $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;
}