Skip to content
Snippets Groups Projects
Forked from Social Cognitive Systems / ipaaca
185 commits behind the upstream repository.
ipaaca-payload.cc 32.09 KiB
/*
 * This file is part of IPAACA, the
 *  "Incremental Processing Architecture
 *   for Artificial Conversational Agents".
 *
 * Copyright (c) 2009-2015 Social Cognitive Systems Group
 *                         (formerly the Sociable Agents Group)
 *                         CITEC, Bielefeld University
 *
 * http://opensource.cit-ec.de/projects/ipaaca/
 * http://purl.org/net/ipaaca
 *
 * This file may be licensed under the terms of of the
 * GNU Lesser General Public License Version 3 (the ``LGPL''),
 * or (at your option) any later version.
 *
 * Software distributed under the License is distributed
 * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
 * express or implied. See the LGPL for the specific language
 * governing rights and limitations.
 *
 * You should have received a copy of the LGPL along with this
 * program. If not, go to http://www.gnu.org/licenses/lgpl.html
 * or write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The development of this software was supported by the
 * Excellence Cluster EXC 277 Cognitive Interaction Technology.
 * The Excellence Cluster EXC 277 is a grant of the Deutsche
 * Forschungsgemeinschaft (DFG) in the context of the German
 * Excellence Initiative.
 */

#include <ipaaca/ipaaca.h>

namespace ipaaca {

using namespace rapidjson;

using namespace rsb;
using namespace rsb::filter;
using namespace rsb::converter;
using namespace rsb::patterns;

// temporary helper to show rapidjson internal type
std::string value_diagnosis(rapidjson::Value* val)
{
	if (!val) return "nullptr";
	if (val->IsNull()) return "null";
	if (val->IsString()) return "string";
	if (val->IsNumber()) return "number";
	if (val->IsBool()) return "bool";
	if (val->IsArray()) return "array";
	if (val->IsObject()) return "object";
	return "other";

}


IPAACA_EXPORT std::ostream& operator<<(std::ostream& os, const rapidjson::Value& val)//{{{
{
	os << json_value_cast<std::string>(val);
	return os;
}
//}}}
IPAACA_EXPORT std::ostream& operator<<(std::ostream& os, const PayloadEntryProxy& proxy)//{{{
{
	if (proxy.json_value) os << *(proxy.json_value);
	else os << "null";
	return os;
}
//}}}
IPAACA_EXPORT std::ostream& operator<<(std::ostream& os, PayloadDocumentEntry::ptr entry)//{{{
{
	os << json_value_cast<std::string>(entry->document);
	return os;
}
//}}}

IPAACA_EXPORT std::ostream& operator<<(std::ostream& os, const Payload& obj)//{{{
{
	os << "{";
	bool first = true;
	for (auto& kv: obj._document_store) {
		if (first) { first=false; } else { os << ", "; }
		os << "\"" << kv.first << "\":" << kv.second->to_json_string_representation() << "";
	}
	os << "}";
	return os;
}
//}}}

double strict_numerical_interpretation(const std::string& str)
{
	char* endptr;
	auto s = str_trim(str);
	const char* startptr = s.c_str();
	double d = strtod(startptr, &endptr);
	if ((*endptr)=='\0') {
		// everything could be parsed
		return d;
	} else {
		throw PayloadTypeConversionError();
	}
}

// json_value_cast//{{{
IPAACA_EXPORT template<> std::string json_value_cast(const rapidjson::Value& v)
{
	if (v.IsString()) return v.GetString();
	if (v.IsNull()) return "";
	rapidjson::StringBuffer buffer;
	rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
	v.Accept(writer);
	return buffer.GetString();
}
IPAACA_EXPORT template<> long json_value_cast(const rapidjson::Value& v)
{
	if (v.IsString()) return (long) strict_numerical_interpretation(v.GetString());
	if (v.IsInt()) return v.GetInt();
	if (v.IsUint()) return v.GetUint();
	if (v.IsInt64()) return v.GetInt64();
	if (v.IsUint64()) return v.GetUint64();
	if (v.IsDouble()) return (long) v.GetDouble();
	if (v.IsBool()) return v.GetBool() ? 1l : 0l;
	if (v.IsNull()) return 0l;
	// default: return parse of string version (should always be 0 though?)
	throw PayloadTypeConversionError();
	/*
	rapidjson::StringBuffer buffer;
	rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
	v.Accept(writer);
	return atol(std::string(buffer.GetString()).c_str());
	*/
}
IPAACA_EXPORT template<> int json_value_cast(const rapidjson::Value& v)
{
	if (v.IsString()) return (int) strict_numerical_interpretation(v.GetString());
	if (v.IsInt()) return v.GetInt();
	if (v.IsUint()) return v.GetUint();
	if (v.IsInt64()) return v.GetInt64();
	if (v.IsUint64()) return v.GetUint64();
	if (v.IsDouble()) return (long) v.GetDouble();
	if (v.IsBool()) return v.GetBool() ? 1l : 0l;
	if (v.IsNull()) return 0l;
	throw PayloadTypeConversionError();
}
IPAACA_EXPORT template<> double json_value_cast(const rapidjson::Value& v)
{
	if (v.IsString()) return strict_numerical_interpretation(v.GetString());
	if (v.IsDouble()) return v.GetDouble();
	if (v.IsInt()) return (double) v.GetInt();
	if (v.IsUint()) return (double) v.GetUint();
	if (v.IsInt64()) return (double) v.GetInt64();
	if (v.IsUint64()) return (double) v.GetUint64();
	if (v.IsBool()) return v.GetBool() ? 1.0 : 0.0;
	if (v.IsNull()) return 0.0;
	throw PayloadTypeConversionError();
}
IPAACA_EXPORT template<> bool json_value_cast(const rapidjson::Value& v)
{
	if (v.IsString()) {
		std::string s = v.GetString();
		return !((s=="")||(s=="false")||(s=="False")||(s=="0"));
		//return ((s=="1")||(s=="true")||(s=="True"));
	}
	if (v.IsBool()) return v.GetBool();
	if (v.IsNull()) return false;
	if (v.IsInt()) return v.GetInt() != 0;
	if (v.IsUint()) return v.GetUint() != 0;
	if (v.IsInt64()) return v.GetInt64() != 0;
	if (v.IsUint64()) return v.GetUint64() != 0;
	if (v.IsDouble()) return v.GetDouble() != 0.0;
	// default: assume "pointer-like" semantics (i.e. objects are TRUE)
	return true;
}
/*
 * std::map<std::string, std::string> result;
	std::for_each(_document_store.begin(), _document_store.end(), [&result](std::pair<std::string, PayloadDocumentEntry::ptr> pair) {
			result[pair.first] =  pair.second->document.GetString();
			});
			*/
//}}}

IPAACA_EXPORT void pack_into_json_value(rapidjson::Value& valueobject, rapidjson::Document::AllocatorType& allocator, int newvalue)
{
	valueobject.SetInt(newvalue);
}
IPAACA_EXPORT void pack_into_json_value(rapidjson::Value& valueobject, rapidjson::Document::AllocatorType& allocator, long newvalue)
{
	valueobject.SetInt(newvalue);
}
IPAACA_EXPORT void pack_into_json_value(rapidjson::Value& valueobject, rapidjson::Document::AllocatorType& allocator, double newvalue)
{
	valueobject.SetDouble(newvalue);
}
IPAACA_EXPORT void pack_into_json_value(rapidjson::Value& valueobject, rapidjson::Document::AllocatorType& allocator, bool newvalue)
{
	valueobject.SetBool(newvalue);
}
IPAACA_EXPORT void pack_into_json_value(rapidjson::Value& valueobject, rapidjson::Document::AllocatorType& allocator, const std::string& newvalue)
{
	valueobject.SetString(newvalue.c_str(), allocator);
}
IPAACA_EXPORT void pack_into_json_value(rapidjson::Value& valueobject, rapidjson::Document::AllocatorType& allocator, const char* newvalue)
{
	valueobject.SetString(newvalue, allocator);
}
/*
IPAACA_EXPORT template<> void pack_into_json_value(rapidjson::Value& valueobject, rapidjson::Document::AllocatorType& allocator, const std::vector<std::string>& newvalue)
{
	valueobject.SetArray();
	for (auto& str: newvalue) {
		rapidjson::Value sv;
		sv.SetString(str, allocator);
		valueobject.PushBack(sv, allocator);
	}
}
IPAACA_EXPORT template<> void pack_into_json_value(rapidjson::Value& valueobject, rapidjson::Document::AllocatorType& allocator, const std::list<std::string>& newvalue)
{
	IPAACA_IMPLEMENT_ME
}
IPAACA_EXPORT template<> void pack_into_json_value(rapidjson::Value& valueobject, rapidjson::Document::AllocatorType& allocator, const std::map<std::string, std::string>& newvalue)
{
	IPAACA_IMPLEMENT_ME
}
*/

// PayloadDocumentEntry//{{{
IPAACA_EXPORT std::string PayloadDocumentEntry::to_json_string_representation()
{
	rapidjson::StringBuffer buffer;
	rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
	document.Accept(writer);
	return buffer.GetString();
}
IPAACA_EXPORT PayloadDocumentEntry::ptr PayloadDocumentEntry::from_json_string_representation(const std::string& json_str)
{
	PayloadDocumentEntry::ptr entry = std::make_shared<ipaaca::PayloadDocumentEntry>();
	if (entry->document.Parse(json_str.c_str()).HasParseError()) {
		throw JsonParsingError();
	}
	//entry->json_source = json_str;
	return entry;
}
IPAACA_EXPORT PayloadDocumentEntry::ptr PayloadDocumentEntry::from_unquoted_string_value(const std::string& str)
{
	PayloadDocumentEntry::ptr entry = std::make_shared<ipaaca::PayloadDocumentEntry>();
	entry->document.SetString(str.c_str(), entry->document.GetAllocator());
	//entry->update_json_source();
	return entry;
}

/// update json_source after a write operation (on newly cloned entries)
/*
IPAACA_EXPORT void PayloadDocumentEntry::update_json_source()
{
	json_source = to_json_string_representation();
}
*/

IPAACA_EXPORT PayloadDocumentEntry::ptr PayloadDocumentEntry::create_null()
{
	PayloadDocumentEntry::ptr entry = std::make_shared<ipaaca::PayloadDocumentEntry>();
	//entry->json_source = "null"; // rapidjson::Document value is also null implicitly
	return entry;
}
IPAACA_EXPORT PayloadDocumentEntry::ptr PayloadDocumentEntry::clone()
{
	//auto entry = PayloadDocumentEntry::from_json_string_representation(this->json_source);
	auto entry = PayloadDocumentEntry::create_null();
	entry->document.CopyFrom(this->document, entry->document.GetAllocator());
	IPAACA_DEBUG("Cloned for copy-on-write, contents: " << entry)
	return entry;
}
IPAACA_EXPORT rapidjson::Value& PayloadDocumentEntry::get_or_create_nested_value_from_proxy_path(PayloadEntryProxy* pep)
{
	if (!(pep->parent)) {
		return document;
	}
	rapidjson::Value& parent_value = get_or_create_nested_value_from_proxy_path(pep->parent);
	if (pep->addressed_as_array) {
		IPAACA_DEBUG("Addressed as array with index " << pep->addressed_index)
		if (! parent_value.IsArray()) {
			throw PayloadAddressingError();
		} else {
			long idx = pep->addressed_index;
			long s = parent_value.Size();
			if (idx<s) {
				return parent_value[idx];
			} else {
				throw PayloadAddressingError();
			}
		}
		// for append / push_back? :
		/*if (parent_value.IsNull()) {
			wasnull = true;
			parent_value.SetArray();
		}
		if (wasnull || parent_value.IsArray()) {
			long idx = pep->addressed_index;
			long s = parent_value.Size();
			if (idx<s) {
				// existing element modified
				parent_value[idx] = *json_value;
			} else {
				// implicitly initialize missing elements to null values
				if (idx>s) {
					long missing_elements = pep->addressed_index - p;
					for (int i=0; i<missing_elements; ++i) {
						parent_value.PushBack(, allocator)
					}
				}
			}
			if (s == 
		} else {
			throw PayloadAddressingError();
		}*/
	} else {
		IPAACA_DEBUG("Addressed as dict with key " << pep->addressed_key)
		// addressed as object (dict)
		//rapidjson::Value& parent_value = *(pep->parent->json_value);
		if (! parent_value.IsObject()) {
			IPAACA_DEBUG("parent is not of type Object")
			throw PayloadAddressingError();
		} else {
			rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
			//Value key;
			//key.SetString(pep->addressed_key, allocator);
			//parent_value.AddMember(key, *json_value, allocator);
			rapidjson::Value key;
			key.SetString(pep->addressed_key, allocator);
			auto it = parent_value.FindMember(key);
			if (it != parent_value.MemberEnd()) {
				return parent_value[key];
			} else {
				rapidjson::Value val;
				parent_value.AddMember(key, val, allocator);
				rapidjson::Value rkey;
				rkey.SetString(pep->addressed_key, allocator);
				return parent_value[rkey];
			}
		}
	}
}

//}}}

// PayloadEntryProxy//{{{
 // only if not top-level
#if 0
IPAACA_EXPORT void PayloadEntryProxy::connect_to_existing_parents()
{
	rapidjson::Document::AllocatorType& allocator = document_entry->document.GetAllocator();
	PayloadEntryProxy* pep = this;
	while (!(pep->existent) && pep->parent) { // only if not top-level
		if (pep->addressed_as_array) {
			rapidjson::Value& parent_value = *(pep->parent->json_value);
			if (! parent_value.IsArray()) {
				throw PayloadAddressingError();
			} else {
				long idx = pep->addressed_index;
				long s = parent_value.Size();
				if (idx<s) {
					parent_value[idx] = *json_value;
				} else {
					throw PayloadAddressingError();
				}
			}
			/*if (parent_value.IsNull()) {
				wasnull = true;
				parent_value.SetArray();
			}
			if (wasnull || parent_value.IsArray()) {
				long idx = pep->addressed_index;
				long s = parent_value.Size();
				if (idx<s) {
					// existing element modified
					parent_value[idx] = *json_value;
				} else {
					// implicitly initialize missing elements to null values
					if (idx>s) {
						long missing_elements = pep->addressed_index - p;
						for (int i=0; i<missing_elements; ++i) {
							parent_value.PushBack(, allocator)
						}
					}
				}
				if (s == 
			} else {
				throw PayloadAddressingError();
			}*/
		} else {
			// addressed as object (dict)
			rapidjson::Value& parent_value = *(pep->parent->json_value);
			if (! parent_value.IsObject()) {
				throw PayloadAddressingError();
			} else {
				Value key;
				key.SetString(pep->addressed_key, allocator);
				parent_value.AddMember(key, *json_value, allocator);
			}
		}
		// repeat for next parent in the tree
		pep = pep->parent;
	}
}
#endif


IPAACA_EXPORT PayloadEntryProxy::PayloadEntryProxy(Payload* payload, const std::string& key)
: _payload(payload), _key(key), parent(nullptr)
{
	document_entry = _payload->get_entry(key);
	json_value = &(document_entry->document);
}
IPAACA_EXPORT PayloadEntryProxy::PayloadEntryProxy(PayloadEntryProxy* parent_, const std::string& addr_key_)
: parent(parent_), addressed_key(addr_key_), addressed_as_array(false)
{
	_payload = parent->_payload;
	_key = parent->_key;
	document_entry = parent->document_entry;
	auto it = parent->json_value->FindMember(addr_key_.c_str());
	if (it != parent->json_value->MemberEnd()) {
		json_value = &(parent->json_value->operator[](addr_key_.c_str()));
		existent = true;
	} else {
		json_value = nullptr; // avoid heap construction here
		existent = false;
	}
}
IPAACA_EXPORT PayloadEntryProxy::PayloadEntryProxy(PayloadEntryProxy* parent_, size_t addr_idx_)
: parent(parent_), addressed_index(addr_idx_), addressed_as_array(true)
{
	_payload = parent->_payload;
	_key = parent->_key;
	document_entry = parent->document_entry;
	json_value = &(parent->json_value->operator[](addr_idx_));
	existent = true;
}

IPAACA_EXPORT PayloadEntryProxy PayloadEntryProxy::operator[](const char* addr_key_)
{
	return operator[](std::string(addr_key_));
}
IPAACA_EXPORT PayloadEntryProxy PayloadEntryProxy::operator[](const std::string& addr_key_)
{
	if (!json_value) {
		IPAACA_DEBUG("Invalid json_value!")
		throw PayloadAddressingError();
	}
	if (! json_value->IsObject()) {
		IPAACA_DEBUG("Expected Object for operator[](string)!")
		throw PayloadAddressingError();
	}
	return PayloadEntryProxy(this, addr_key_);
}
IPAACA_EXPORT PayloadEntryProxy PayloadEntryProxy::operator[](size_t addr_idx_)
{
	if (!json_value) {
		IPAACA_DEBUG("Invalid json_value!")
		throw PayloadAddressingError();
	}
	if (! json_value->IsArray()) {
		IPAACA_DEBUG("Expected Array for operator[](size_t)!")
		throw PayloadAddressingError();
	}
	long s = json_value->Size();
	if (addr_idx_>=s) {
		IPAACA_DEBUG("Array out of bounds!")
		throw PayloadAddressingError();
	}
	return PayloadEntryProxy(this, addr_idx_);
}
IPAACA_EXPORT PayloadEntryProxy PayloadEntryProxy::operator[](int addr_idx_)
{
	if (addr_idx_ < 0) {
		IPAACA_DEBUG("Negative index!")
		throw PayloadAddressingError();
	}
	return operator[]((size_t) addr_idx_);
}

IPAACA_EXPORT PayloadEntryProxy& PayloadEntryProxy::operator=(const PayloadEntryProxy& otherproxy)
{
	PayloadDocumentEntry::ptr new_entry = document_entry->clone(); // copy-on-write, no lock required
	rapidjson::Value& newval = new_entry->get_or_create_nested_value_from_proxy_path(this);
	auto valueptr = otherproxy.json_value;
	if (valueptr) { // only set if value is valid, keep default null value otherwise
		newval.CopyFrom(*valueptr, new_entry->document.GetAllocator());
	}
	//new_entry->update_json_source();
	_payload->set(_key, new_entry);
	return *this;
}

/*
IPAACA_EXPORT PayloadEntryProxy& PayloadEntryProxy::operator=(const std::string& value)
{
	//std::cout << "operator=(string)" << std::endl;
	IPAACA_IMPLEMENT_ME
	//_payload->set(_key, value);
	return *this;
}
IPAACA_EXPORT PayloadEntryProxy& PayloadEntryProxy::operator=(const char* value)
{
	//std::cout << "operator=(const char*)" << std::endl;
	IPAACA_IMPLEMENT_ME
	//_payload->set(_key, value);
	return *this;
}
IPAACA_EXPORT PayloadEntryProxy& PayloadEntryProxy::operator=(double value)
{
	//std::cout << "operator=(double)" << std::endl;
	IPAACA_IMPLEMENT_ME
	//_payload->set(_key, boost::lexical_cast<std::string>(value));
	return *this;
}
IPAACA_EXPORT PayloadEntryProxy& PayloadEntryProxy::operator=(bool value)
{
	//std::cout << "operator=(bool)" << std::endl;
	IPAACA_IMPLEMENT_ME
	//_payload->set(_key, boost::lexical_cast<std::string>(value));
	return *this;
}
*/

IPAACA_EXPORT PayloadEntryProxy::operator std::string()
{
	return json_value_cast<std::string>(json_value);
	//PayloadEntryProxy::get<std::string>();
}
IPAACA_EXPORT PayloadEntryProxy::operator long()
{
	return json_value_cast<long>(json_value);
	//return PayloadEntryProxy::get<long>();
}
IPAACA_EXPORT PayloadEntryProxy::operator double()
{
	return json_value_cast<double>(json_value);
	//return PayloadEntryProxy::get<double>();
}
IPAACA_EXPORT PayloadEntryProxy::operator bool()
{
	return json_value_cast<bool>(json_value);
	//return PayloadEntryProxy::get<bool>();
}
IPAACA_EXPORT std::string PayloadEntryProxy::to_str()
{
	return json_value_cast<std::string>(json_value);
	//return PayloadEntryProxy::get<std::string>(); 
}
IPAACA_EXPORT long PayloadEntryProxy::to_long()
{
	return json_value_cast<long>(json_value);
	//return PayloadEntryProxy::get<long>();
}
IPAACA_EXPORT double PayloadEntryProxy::to_float()
{
	return json_value_cast<double>(json_value);
	//return PayloadEntryProxy::get<double>();
}
IPAACA_EXPORT bool PayloadEntryProxy::to_bool()
{
	return json_value_cast<bool>(json_value);
	//return PayloadEntryProxy::get<bool>();
}

IPAACA_EXPORT PayloadEntryProxyMapDecorator PayloadEntryProxy::as_map()
{
	if (json_value && json_value->IsObject()) return PayloadEntryProxyMapDecorator(this);
	throw PayloadTypeConversionError();
}

IPAACA_EXPORT PayloadEntryProxyListDecorator PayloadEntryProxy::as_list()
{
	if (json_value && json_value->IsArray()) return PayloadEntryProxyListDecorator(this);
	throw PayloadTypeConversionError();
}

IPAACA_EXPORT size_t PayloadEntryProxy::size()
{
	if (!json_value) return 0;
	if (json_value->IsArray()) return json_value->Size();
	if (json_value->IsObject()) return json_value->MemberCount();
	return 0;
}
IPAACA_EXPORT bool PayloadEntryProxy::is_null()
{
	return (!json_value) || json_value->IsNull();
}
IPAACA_EXPORT bool PayloadEntryProxy::is_string()
{
	return json_value && json_value->IsString();
}
/// is_number => whether it is *interpretable* as
/// a numerical value (i.e. including conversions)
IPAACA_EXPORT bool PayloadEntryProxy::is_number()
{
	if (!json_value) return false;
	try {
		double dummy = json_value_cast<double>(*json_value);
		return true;
	} catch (PayloadTypeConversionError& ex) {
		return false;
	}
}
IPAACA_EXPORT bool PayloadEntryProxy::is_list()
{
	return json_value && json_value->IsArray();
}
IPAACA_EXPORT bool PayloadEntryProxy::is_map()
{
	return json_value && json_value->IsObject();
}

//
// new stuff for protocol v2
//

/*
IPAACA_EXPORT template<> std::string PayloadEntryProxy::get<std::string>()
{
	if (!json_value) return "";
	//IPAACA_INFO( value_diagnosis(json_value) )
	if (json_value->IsString()) return json_value->GetString();
	if (json_value->IsNull()) return "";
	rapidjson::StringBuffer buffer;
	rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
	json_value->Accept(writer);
	return buffer.GetString();
	
	//return _payload->get(_key);
}
IPAACA_EXPORT template<> long PayloadEntryProxy::get<long>()
{
	return atof(operator std::string().c_str());
}
IPAACA_EXPORT template<> double PayloadEntryProxy::get<double>()
{
	return atol(operator std::string().c_str());
}
IPAACA_EXPORT template<> bool PayloadEntryProxy::get<bool>()
{
	std::string s = operator std::string();
	return ((s=="1")||(s=="true")||(s=="True"));
}
// complex types
IPAACA_EXPORT template<> std::list<std::string> PayloadEntryProxy::get<std::list<std::string> >()
{
	std::list<std::string> l;
	l.push_back(PayloadEntryProxy::get<std::string>());
	return l;
}
IPAACA_EXPORT template<> std::vector<std::string> PayloadEntryProxy::get<std::vector<std::string> >()
{
	std::vector<std::string> v;
	v.push_back(PayloadEntryProxy::get<std::string>());
	return v;
}
IPAACA_EXPORT template<> std::map<std::string, std::string> PayloadEntryProxy::get<std::map<std::string, std::string> >()
{
	std::map<std::string, std::string> m;
	m["__automatic__"] = PayloadEntryProxy::get<std::string>();
	return m;
}
*/

//}}}

// Payload//{{{

IPAACA_EXPORT void Payload::on_lock()
{
	Locker locker(_payload_operation_mode_lock);
	IPAACA_DEBUG("Starting batch update mode ...")
	_update_on_every_change = false;
}
IPAACA_EXPORT void Payload::on_unlock()
{
	Locker locker(_payload_operation_mode_lock);
	IPAACA_DEBUG("... applying batch update with " << _collected_modifications.size() << " modifications and " << _collected_removals.size() << " removals ...")
	_internal_merge_and_remove(_collected_modifications, _collected_removals, _batch_update_writer_name);
	_update_on_every_change = true;
	_batch_update_writer_name = "";
	_collected_modifications.clear();
	_collected_removals.clear();
	IPAACA_DEBUG("... exiting batch update mode.")
}

IPAACA_EXPORT void Payload::initialize(boost::shared_ptr<IUInterface> iu)
{
	_iu = boost::weak_ptr<IUInterface>(iu);
}

IPAACA_EXPORT PayloadEntryProxy Payload::operator[](const std::string& key)
{
	// TODO atomicize
	//boost::shared_ptr<PayloadEntryProxy> p(new PayloadEntryProxy(this, key));
	return PayloadEntryProxy(this, key);
}

IPAACA_EXPORT Payload::operator std::map<std::string, std::string>()
{
	std::map<std::string, std::string> result;
	std::for_each(_document_store.begin(), _document_store.end(), [&result](std::pair<std::string, PayloadDocumentEntry::ptr> pair) {
			result[pair.first] =  json_value_cast<std::string>(pair.second->document);
			});
	return result;
}

IPAACA_EXPORT void Payload::_internal_set(const std::string& k, PayloadDocumentEntry::ptr v, const std::string& writer_name) {
	Locker locker(_payload_operation_mode_lock);
	if (_update_on_every_change) {
		std::map<std::string, PayloadDocumentEntry::ptr> _new;
		std::vector<std::string> _remove;
		_new[k] = v;
		_iu.lock()->_modify_payload(true, _new, _remove, writer_name );
		IPAACA_DEBUG(" Setting local payload item \"" << k << "\" to " << v)
		_document_store[k] = v;
		mark_revision_change();
	} else {
		IPAACA_DEBUG("queueing a payload set operation")
		_batch_update_writer_name = writer_name;
		_collected_modifications[k] = v;
		// revoke deletions of this updated key
		std::vector<std::string> new_removals;
		for (auto& rk: _collected_removals) {
			if (rk!=k) new_removals.push_back(rk);
		}
		_collected_removals = new_removals;
	}
}
IPAACA_EXPORT void Payload::_internal_remove(const std::string& k, const std::string& writer_name) {
	Locker locker(_payload_operation_mode_lock);
	if (_update_on_every_change) {
		std::map<std::string, PayloadDocumentEntry::ptr> _new;
		std::vector<std::string> _remove;
		_remove.push_back(k);
		_iu.lock()->_modify_payload(true, _new, _remove, writer_name );
		_document_store.erase(k);
		mark_revision_change();
	} else {
		IPAACA_DEBUG("queueing a payload remove operation")
		_batch_update_writer_name = writer_name;
		_collected_removals.push_back(k);
		// revoke updates of this deleted key
		_collected_modifications.erase(k);
	}
}
IPAACA_EXPORT void Payload::_internal_replace_all(const std::map<std::string, PayloadDocumentEntry::ptr>& new_contents, const std::string& writer_name)
{
	Locker locker(_payload_operation_mode_lock);
	if (_update_on_every_change) {
		std::vector<std::string> _remove;
		_iu.lock()->_modify_payload(false, new_contents, _remove, writer_name );
		_document_store = new_contents;
		mark_revision_change();
	} else {
		IPAACA_DEBUG("queueing a payload replace_all operation")
		_batch_update_writer_name = writer_name;
		_collected_modifications.clear();
		for (auto& kv: new_contents) {
			_collected_modifications[kv.first] = kv.second;
		}
		// take all existing keys and flag to remove them, unless overridden in current update
		for (auto& kv: _document_store) {
			if (! new_contents.count(kv.first)) {
				_collected_removals.push_back(kv.first);
				_collected_modifications.erase(kv.first);
			}
		}
	}
}
IPAACA_EXPORT void Payload::_internal_merge(const std::map<std::string, PayloadDocumentEntry::ptr>& contents_to_merge, const std::string& writer_name)
{
	Locker locker(_payload_operation_mode_lock);
	if (_update_on_every_change) {
		std::vector<std::string> _remove;
		_iu.lock()->_modify_payload(true, contents_to_merge, _remove, writer_name );
		for (auto& kv: contents_to_merge) {
			_document_store[kv.first] = kv.second;
		}
		mark_revision_change();
	} else {
		IPAACA_DEBUG("queueing a payload merge operation")
		std::set<std::string> updated_keys;
		_batch_update_writer_name = writer_name;
		for (auto& kv: contents_to_merge) {
			_collected_modifications[kv.first] = kv.second;
			updated_keys.insert(kv.first);
		}
		// revoke deletions of updated keys
		std::vector<std::string> new_removals;
		for (auto& rk: _collected_removals) {
			if (! updated_keys.count(rk)) new_removals.push_back(rk);
		}
		_collected_removals = new_removals;
	}
}
IPAACA_EXPORT void Payload::_internal_merge_and_remove(const std::map<std::string, PayloadDocumentEntry::ptr>& contents_to_merge, const std::vector<std::string>& keys_to_remove, const std::string& writer_name)
{
	// this function is called by exiting the batch update mode only, so no extra locking here
	_iu.lock()->_modify_payload(true, contents_to_merge, keys_to_remove, writer_name );
	for (auto& k: keys_to_remove) {
		_document_store.erase(k);
	}
	for (auto& kv: contents_to_merge) {
		_document_store[kv.first] = kv.second;
	}
	mark_revision_change();
}
IPAACA_EXPORT PayloadDocumentEntry::ptr Payload::get_entry(const std::string& k) {
	if (_document_store.count(k)>0) return _document_store[k];
	else return PayloadDocumentEntry::create_null();  // contains Document with 'null' value
}
IPAACA_EXPORT std::string Payload::get(const std::string& k) { // DEPRECATED
	if (_document_store.count(k)>0) return _document_store[k]->document.GetString();
	return "";
}

IPAACA_EXPORT void Payload::set(const std::map<std::string, std::string>& all_elems)
{
	std::map<std::string, PayloadDocumentEntry::ptr> newmap;
	for (auto& kv: all_elems) {
		/*PayloadDocumentEntry::ptr newit = PayloadDocumentEntry::create_null();
		newit->document.SetString(kv.second, newit->document.GetAllocator());
		newit->update_json_source();
		newmap[kv.first] = newit;*/
		newmap[kv.first] = PayloadDocumentEntry::from_unquoted_string_value(kv.second);
	}
	_internal_replace_all(newmap);
}

IPAACA_EXPORT void Payload::_remotely_enforced_wipe()
{
	_document_store.clear();
	mark_revision_change();
}
IPAACA_EXPORT void Payload::_remotely_enforced_delitem(const std::string& k)
{
	_document_store.erase(k);
	mark_revision_change();
}
IPAACA_EXPORT void Payload::_remotely_enforced_setitem(const std::string& k, PayloadDocumentEntry::ptr entry)
{
	_document_store[k] = entry;
	mark_revision_change();
}
IPAACA_EXPORT PayloadIterator Payload::begin()
{
	return PayloadIterator(this, _document_store.begin());
}
IPAACA_EXPORT PayloadIterator Payload::end()
{
	return PayloadIterator(this, _document_store.end());
}

//}}}

// PayloadIterator//{{{
IPAACA_EXPORT PayloadIterator::PayloadIterator(Payload* payload, PayloadDocumentStore::iterator&& ref_it)
: _payload(payload), reference_payload_revision(payload->internal_revision), raw_iterator(std::move(ref_it))
{
}
IPAACA_EXPORT PayloadIterator::PayloadIterator(const PayloadIterator& iter)
: _payload(iter._payload), reference_payload_revision(iter.reference_payload_revision), raw_iterator(iter.raw_iterator)
{
}

IPAACA_EXPORT PayloadIterator& PayloadIterator::operator++()
{
	if (_payload->revision_changed(reference_payload_revision)) throw PayloadIteratorInvalidError();
	++raw_iterator;
	return *this;
}

IPAACA_EXPORT std::pair<std::string, PayloadEntryProxy> PayloadIterator::operator*()
{
	if (_payload->revision_changed(reference_payload_revision)) throw PayloadIteratorInvalidError();
	if (raw_iterator == _payload->_document_store.end()) throw PayloadIteratorInvalidError();
	return std::pair<std::string, PayloadEntryProxy>(raw_iterator->first, PayloadEntryProxy(_payload, raw_iterator->first));
}
IPAACA_EXPORT std::shared_ptr<std::pair<std::string, PayloadEntryProxy> > PayloadIterator::operator->()
{
	if (_payload->revision_changed(reference_payload_revision)) throw PayloadIteratorInvalidError();
	if (raw_iterator == _payload->_document_store.end()) throw PayloadIteratorInvalidError();
	return std::make_shared<std::pair<std::string, PayloadEntryProxy> >(raw_iterator->first, PayloadEntryProxy(_payload, raw_iterator->first));
}

IPAACA_EXPORT bool PayloadIterator::operator==(const PayloadIterator& ref)
{
	if (_payload->revision_changed(reference_payload_revision)) throw PayloadIteratorInvalidError();
	return (raw_iterator==ref.raw_iterator);
}
IPAACA_EXPORT bool PayloadIterator::operator!=(const PayloadIterator& ref)
{
	if (_payload->revision_changed(reference_payload_revision)) throw PayloadIteratorInvalidError();
	return (raw_iterator!=ref.raw_iterator);
}
//}}}

// PayloadEntryProxyMapIterator//{{{
IPAACA_EXPORT PayloadEntryProxyMapIterator::PayloadEntryProxyMapIterator(PayloadEntryProxy* proxy_, RawIterator&& raw_iter)
: proxy(proxy_), raw_iterator(std::move(raw_iter))
{
}

IPAACA_EXPORT PayloadEntryProxyMapIterator& PayloadEntryProxyMapIterator::operator++()
{
	// prevent increase beyond end() ?
	raw_iterator++;
	return *this;
}

IPAACA_EXPORT std::pair<std::string, PayloadEntryProxy> PayloadEntryProxyMapIterator::operator*()
{
	std::string key = raw_iterator->name.GetString();
	return std::pair<std::string, PayloadEntryProxy>(key, (*proxy)[key] ); // generates child Proxy
}

IPAACA_EXPORT std::shared_ptr<std::pair<std::string, PayloadEntryProxy> > PayloadEntryProxyMapIterator::operator->()
{
	std::string key = raw_iterator->name.GetString();
	return std::make_shared<std::pair<std::string, PayloadEntryProxy> >(key, (*proxy)[key] ); // generates child Proxy
}
IPAACA_EXPORT bool PayloadEntryProxyMapIterator::operator==(const PayloadEntryProxyMapIterator& other_iter)
{
	return raw_iterator==other_iter.raw_iterator;
}
IPAACA_EXPORT bool PayloadEntryProxyMapIterator::operator!=(const PayloadEntryProxyMapIterator& other_iter)
{
	return raw_iterator!=other_iter.raw_iterator;
}
//}}}
// PayloadEntryProxyMapDecorator//{{{
PayloadEntryProxyMapIterator PayloadEntryProxyMapDecorator::begin()
{
	return PayloadEntryProxyMapIterator(proxy, proxy->json_value->MemberBegin());
}
PayloadEntryProxyMapIterator PayloadEntryProxyMapDecorator::end()
{
	return PayloadEntryProxyMapIterator(proxy, proxy->json_value->MemberEnd());
}
//}}}

// PayloadEntryProxyListIterator//{{{
IPAACA_EXPORT PayloadEntryProxyListIterator::PayloadEntryProxyListIterator(PayloadEntryProxy* proxy_, size_t idx, size_t size_)
: proxy(proxy_), current_idx(idx), size(size_)
{
}

IPAACA_EXPORT PayloadEntryProxyListIterator& PayloadEntryProxyListIterator::operator++()
{
	if (current_idx!=size) current_idx++;
	return *this;
}

IPAACA_EXPORT PayloadEntryProxy PayloadEntryProxyListIterator::operator*()
{
	return (*proxy)[current_idx];
}

IPAACA_EXPORT std::shared_ptr<PayloadEntryProxy> PayloadEntryProxyListIterator::operator->()
{
	return std::make_shared<PayloadEntryProxy>((*proxy)[current_idx]);
}
IPAACA_EXPORT bool PayloadEntryProxyListIterator::operator==(const PayloadEntryProxyListIterator& other_iter)
{
	return (proxy==other_iter.proxy) && (current_idx==other_iter.current_idx);
}
IPAACA_EXPORT bool PayloadEntryProxyListIterator::operator!=(const PayloadEntryProxyListIterator& other_iter)
{
	return (current_idx!=other_iter.current_idx) || (proxy!=other_iter.proxy);
}
//}}}
// PayloadEntryProxyListDecorator//{{{
PayloadEntryProxyListIterator PayloadEntryProxyListDecorator::begin()
{
	return PayloadEntryProxyListIterator(proxy, 0, proxy->json_value->Size());
}
PayloadEntryProxyListIterator PayloadEntryProxyListDecorator::end()
{
	size_t size = proxy->json_value->Size();
	return PayloadEntryProxyListIterator(proxy, size, size);
}
//}}}

} // of namespace ipaaca