From e0578b8fc582d431cce61ecac833ecf7031f6e75 Mon Sep 17 00:00:00 2001
From: Jan Kaluza <hanzz.k@gmail.com>
Date: Tue, 12 Apr 2011 13:36:52 +0200
Subject: Added Roster Item Exchange XEP support.

License: This patch is BSD-licensed, see http://www.opensource.org/licenses/bsd-license.php

diff --git a/Swiften/Elements/RosterItemExchangePayload.cpp b/Swiften/Elements/RosterItemExchangePayload.cpp
new file mode 100644
index 0000000..846e184
--- /dev/null
+++ b/Swiften/Elements/RosterItemExchangePayload.cpp
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2011 Jan Kaluza
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include "Swiften/Elements/RosterItemExchangePayload.h"
+#include "Swiften/Base/foreach.h"
+
+namespace Swift {
+
+RosterItemExchangePayload::RosterItemExchangePayload() {
+}
+
+}
diff --git a/Swiften/Elements/RosterItemExchangePayload.h b/Swiften/Elements/RosterItemExchangePayload.h
new file mode 100644
index 0000000..f573039
--- /dev/null
+++ b/Swiften/Elements/RosterItemExchangePayload.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2011 Jan Kaluza
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+#include <string>
+#include <boost/optional.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include "Swiften/Elements/Payload.h"
+#include "Swiften/JID/JID.h"
+
+
+namespace Swift {
+	class RosterItemExchangePayload : public Payload {
+		public:
+			typedef boost::shared_ptr<RosterItemExchangePayload> ref;
+
+			enum Action { Add, Modify, Delete };
+
+			struct Item {
+				Action action;
+				JID jid;
+				std::string name;
+				std::vector<std::string> groups;
+			};
+
+			typedef std::vector<RosterItemExchangePayload::Item> RosterItemExchangePayloadItems;
+
+		public:
+			RosterItemExchangePayload();
+
+			void addItem(const RosterItemExchangePayload::Item& item) {
+				items_.push_back(item);
+			}
+
+			const RosterItemExchangePayloadItems& getItems() const {
+				return items_;
+			}
+
+		private:
+			RosterItemExchangePayloadItems items_;
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
index e20c06d..5052e67 100644
--- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
+++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
@@ -17,6 +17,7 @@
 #include "Swiften/Parser/PayloadParsers/StartSessionParser.h"
 #include "Swiften/Parser/PayloadParsers/StatusParser.h"
 #include "Swiften/Parser/PayloadParsers/StatusShowParser.h"
+#include "Swiften/Parser/PayloadParsers/RosterItemExchangeParser.h"
 #include "Swiften/Parser/PayloadParsers/RosterParser.h"
 #include "Swiften/Parser/PayloadParsers/SoftwareVersionParser.h"
 #include "Swiften/Parser/PayloadParsers/StorageParser.h"
@@ -54,6 +55,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<ErrorParser>("error")));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<SoftwareVersionParser>("query", "jabber:iq:version")));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<StorageParser>("storage", "storage:bookmarks")));
+	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<RosterItemExchangeParser>("x", "http://jabber.org/protocol/rosterx")));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<RosterParser>("query", "jabber:iq:roster")));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<DiscoInfoParser>("query", "http://jabber.org/protocol/disco#info")));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<DiscoItemsParser>("query", "http://jabber.org/protocol/disco#items")));
diff --git a/Swiften/Parser/PayloadParsers/RosterItemExchangeParser.cpp b/Swiften/Parser/PayloadParsers/RosterItemExchangeParser.cpp
new file mode 100644
index 0000000..7d59cc3
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/RosterItemExchangeParser.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2011 Jan Kaluza
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include "Swiften/Parser/PayloadParsers/RosterItemExchangeParser.h"
+#include "Swiften/Parser/SerializingParser.h"
+
+namespace Swift {
+
+RosterItemExchangeParser::RosterItemExchangeParser() : level_(TopLevel), inItem_(false) {
+}
+
+void RosterItemExchangeParser::handleStartElement(const std::string& element, const std::string& /*ns*/, const AttributeMap& attributes) {
+	if (level_ == PayloadLevel) {
+		if (element == "item") {
+			inItem_ = true;
+
+			currentItem_ = RosterItemExchangePayload::Item();
+
+			currentItem_.jid = JID(attributes.getAttribute("jid"));
+			currentItem_.name = attributes.getAttribute("name");
+
+			std::string action = attributes.getAttribute("action");
+			if (action == "add") {
+				currentItem_.action = RosterItemExchangePayload::Add;
+			}
+			else if (action == "modify") {
+				currentItem_.action = RosterItemExchangePayload::Modify;
+			}
+			else if (action == "delete") {
+				currentItem_.action = RosterItemExchangePayload::Delete;
+			}
+			else {
+				// Add is default action according to XEP
+				currentItem_.action = RosterItemExchangePayload::Add;
+			}
+		}
+	}
+	else if (level_ == ItemLevel) {
+		if (element == "group") {
+			currentText_ = "";
+		}
+	}
+	++level_;
+}
+
+void RosterItemExchangeParser::handleEndElement(const std::string& element, const std::string& /*ns*/) {
+	--level_;
+	if (level_ == PayloadLevel) {
+		if (inItem_) {
+			getPayloadInternal()->addItem(currentItem_);
+			inItem_ = false;
+		}
+	}
+	else if (level_ == ItemLevel) {
+		if (element == "group") {
+			currentItem_.groups.push_back(currentText_);
+		}
+	}
+}
+
+void RosterItemExchangeParser::handleCharacterData(const std::string& data) {
+	currentText_ += data;
+}
+
+}
diff --git a/Swiften/Parser/PayloadParsers/RosterItemExchangeParser.h b/Swiften/Parser/PayloadParsers/RosterItemExchangeParser.h
new file mode 100644
index 0000000..3d6b8f4
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/RosterItemExchangeParser.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2011 Jan Kaluza
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Elements/RosterItemExchangePayload.h"
+#include "Swiften/Parser/GenericPayloadParser.h"
+
+namespace Swift {
+	class SerializingParser;
+
+	class RosterItemExchangeParser : public GenericPayloadParser<RosterItemExchangePayload> {
+		public:
+			RosterItemExchangeParser();
+
+			virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes);
+			virtual void handleEndElement(const std::string& element, const std::string&);
+			virtual void handleCharacterData(const std::string& data);
+
+		private:
+			enum Level { 
+				TopLevel = 0, 
+				PayloadLevel = 1,
+				ItemLevel = 2
+			};
+			int level_;
+			bool inItem_;
+			RosterItemExchangePayload::Item currentItem_;
+			std::string currentText_;
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/UnitTest/RosterItemExchangeParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/RosterItemExchangeParserTest.cpp
new file mode 100644
index 0000000..ceba45f
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/UnitTest/RosterItemExchangeParserTest.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2011 Jan Kaluza
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include "Swiften/Parser/PayloadParsers/RosterItemExchangeParser.h"
+#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h"
+
+using namespace Swift;
+
+class RosterItemExchangeParserTest : public CppUnit::TestFixture
+{
+		CPPUNIT_TEST_SUITE(RosterItemExchangeParserTest);
+		CPPUNIT_TEST(testParse);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void testParse() {
+			PayloadsParserTester parser;
+			CPPUNIT_ASSERT(parser.parse(
+				"<x xmlns=\"http://jabber.org/protocol/rosterx\">"
+					"<item action=\"add\" jid=\"foo@bar.com\" name=\"Foo @ Bar\">"
+						"<group>Group 1</group>"
+						"<group>Group 2</group>"
+					"</item>"
+					"<item action=\"modify\" jid=\"baz@blo.com\" name=\"Baz\"/>"
+				"</x>"));
+
+			RosterItemExchangePayload* payload = dynamic_cast<RosterItemExchangePayload*>(parser.getPayload().get());
+			const RosterItemExchangePayload::RosterItemExchangePayloadItems& items = payload->getItems();
+
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), items.size());
+
+			CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com"), items[0].jid);
+			CPPUNIT_ASSERT_EQUAL(std::string("Foo @ Bar"), items[0].name);
+			CPPUNIT_ASSERT_EQUAL(RosterItemExchangePayload::Add, items[0].action);
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), items[0].groups.size());
+			CPPUNIT_ASSERT_EQUAL(std::string("Group 1"), items[0].groups[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("Group 2"), items[0].groups[1]);
+
+			CPPUNIT_ASSERT_EQUAL(JID("baz@blo.com"), items[1].jid);
+			CPPUNIT_ASSERT_EQUAL(std::string("Baz"), items[1].name);
+			CPPUNIT_ASSERT_EQUAL(RosterItemExchangePayload::Modify, items[1].action);
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), items[1].groups.size());
+		}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(RosterItemExchangeParserTest);
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index 2bd5aff..03e4208 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -38,6 +38,7 @@ sources = [
 		"PayloadParsers/PrivateStorageParser.cpp",
 		"PayloadParsers/RawXMLPayloadParser.cpp",
 		"PayloadParsers/ResourceBindParser.cpp",
+		"PayloadParsers/RosterItemExchangeParser.cpp",
 		"PayloadParsers/RosterParser.cpp",
 		"PayloadParsers/SecurityLabelParser.cpp",
 		"PayloadParsers/SecurityLabelsCatalogParser.cpp",
diff --git a/Swiften/Roster/SetRosterRequest.h b/Swiften/Roster/SetRosterRequest.h
index e5ae974..2066089 100644
--- a/Swiften/Roster/SetRosterRequest.h
+++ b/Swiften/Roster/SetRosterRequest.h
@@ -18,12 +18,12 @@ namespace Swift {
 		public:
 			typedef boost::shared_ptr<SetRosterRequest> ref;
 
-			static ref create(RosterPayload::ref payload, IQRouter* router) {
-				return ref(new SetRosterRequest(payload, router));
+			static ref create(RosterPayload::ref payload, IQRouter* router, const JID& to = JID()) {
+				return ref(new SetRosterRequest(payload, router, to));
 			}
 
 		private:
-			SetRosterRequest(boost::shared_ptr<RosterPayload> payload, IQRouter* router) : Request(IQ::Set, JID(), boost::shared_ptr<RosterPayload>(payload), router) {
+			SetRosterRequest(boost::shared_ptr<RosterPayload> payload, IQRouter* router, const JID& to) : Request(IQ::Set, to, boost::shared_ptr<RosterPayload>(payload), router) {
 			}
 
 			virtual void handleResponse(boost::shared_ptr<Payload> /*payload*/, ErrorPayload::ref error) {
diff --git a/Swiften/SConscript b/Swiften/SConscript
index d66bfb3..57c95fc 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -73,6 +73,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Elements/Element.cpp",
 			"Elements/IQ.cpp",
 			"Elements/Payload.cpp",
+			"Elements/RosterItemExchangePayload.cpp",
 			"Elements/RosterPayload.cpp",
 			"Elements/Stanza.cpp",
 			"Elements/VCard.cpp",
@@ -117,6 +118,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp",
 			"Serializer/PayloadSerializers/MUCOwnerPayloadSerializer.cpp",
 			"Serializer/PayloadSerializers/ResourceBindSerializer.cpp",
+			"Serializer/PayloadSerializers/RosterItemExchangeSerializer.cpp",
 			"Serializer/PayloadSerializers/RosterSerializer.cpp",
 			"Serializer/PayloadSerializers/SecurityLabelSerializer.cpp",
 			"Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.cpp",
@@ -235,6 +237,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("Parser/PayloadParsers/UnitTest/PriorityParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/RawXMLPayloadParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/ResourceBindParserTest.cpp"),
+			File("Parser/PayloadParsers/UnitTest/RosterItemExchangeParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/RosterParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/IBBParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/SearchPayloadParserTest.cpp"),
@@ -276,6 +279,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("Serializer/PayloadSerializers/UnitTest/ErrorSerializerTest.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/PrioritySerializerTest.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/ResourceBindSerializerTest.cpp"),
+			File("Serializer/PayloadSerializers/UnitTest/RosterItemExchangeSerializerTest.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/SearchPayloadSerializerTest.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/SecurityLabelSerializerTest.cpp"),
diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
index 1bbcbf2..0f05580 100644
--- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
+++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
@@ -14,6 +14,7 @@
 #include "Swiften/Serializer/PayloadSerializers/PrioritySerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/ErrorSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/RosterSerializer.h"
+#include "Swiften/Serializer/PayloadSerializers/RosterItemExchangeSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/MUCOwnerPayloadSerializer.h"
@@ -51,6 +52,7 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() {
 	serializers_.push_back(new PrioritySerializer());
 	serializers_.push_back(new ErrorSerializer());
 	serializers_.push_back(new RosterSerializer());
+	serializers_.push_back(new RosterItemExchangeSerializer());
 	serializers_.push_back(new MUCPayloadSerializer());
 	serializers_.push_back(new MUCUserPayloadSerializer());
 	serializers_.push_back(new MUCOwnerPayloadSerializer(this));
diff --git a/Swiften/Serializer/PayloadSerializers/RosterItemExchangeSerializer.cpp b/Swiften/Serializer/PayloadSerializers/RosterItemExchangeSerializer.cpp
new file mode 100644
index 0000000..76c742c
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/RosterItemExchangeSerializer.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2011 Jan Kaluza
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include "Swiften/Serializer/PayloadSerializers/RosterItemExchangeSerializer.h"
+
+#include <boost/shared_ptr.hpp>
+
+#include "Swiften/Base/foreach.h"
+#include "Swiften/Serializer/XML/XMLTextNode.h"
+#include "Swiften/Serializer/XML/XMLRawTextNode.h"
+#include "Swiften/Serializer/XML/XMLElement.h"
+
+namespace Swift {
+
+RosterItemExchangeSerializer::RosterItemExchangeSerializer() : GenericPayloadSerializer<RosterItemExchangePayload>() {
+}
+
+std::string RosterItemExchangeSerializer::serializePayload(boost::shared_ptr<RosterItemExchangePayload> roster)  const {
+	XMLElement queryElement("x", "http://jabber.org/protocol/rosterx");
+	foreach(const RosterItemExchangePayload::Item& item, roster->getItems()) {
+		boost::shared_ptr<XMLElement> itemElement(new XMLElement("item"));
+		itemElement->setAttribute("jid", item.jid);
+		itemElement->setAttribute("name", item.name);
+
+		switch (item.action) {
+			case RosterItemExchangePayload::Add: itemElement->setAttribute("action", "add"); break;
+			case RosterItemExchangePayload::Modify: itemElement->setAttribute("action", "modify"); break;
+			case RosterItemExchangePayload::Delete: itemElement->setAttribute("action", "delete"); break;
+			default: itemElement->setAttribute("action", "add"); break;
+		}
+
+		foreach(const std::string& group, item.groups) {
+			boost::shared_ptr<XMLElement> groupElement(new XMLElement("group"));
+			groupElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(group)));
+			itemElement->addNode(groupElement);
+		}
+
+		queryElement.addNode(itemElement);
+	}
+
+	return queryElement.serialize();
+}
+
+}
diff --git a/Swiften/Serializer/PayloadSerializers/RosterItemExchangeSerializer.h b/Swiften/Serializer/PayloadSerializers/RosterItemExchangeSerializer.h
new file mode 100644
index 0000000..ec2cc13
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/RosterItemExchangeSerializer.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2011 Jan Kaluza
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Serializer/GenericPayloadSerializer.h"
+#include "Swiften/Elements/RosterItemExchangePayload.h"
+
+namespace Swift {
+	class RosterItemExchangeSerializer : public GenericPayloadSerializer<RosterItemExchangePayload> {
+		public:
+			RosterItemExchangeSerializer();
+
+			virtual std::string serializePayload(boost::shared_ptr<RosterItemExchangePayload>)  const;
+	};
+}
diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/RosterItemExchangeSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/RosterItemExchangeSerializerTest.cpp
new file mode 100644
index 0000000..0fb44c9
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/UnitTest/RosterItemExchangeSerializerTest.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2011 Jan Kaluza
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include "Swiften/Serializer/PayloadSerializers/RosterItemExchangeSerializer.h"
+
+using namespace Swift;
+
+class RosterItemExchangeSerializerTest : public CppUnit::TestFixture
+{
+		CPPUNIT_TEST_SUITE(RosterItemExchangeSerializerTest);
+		CPPUNIT_TEST(testSerialize);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		RosterItemExchangeSerializerTest() {}
+
+		void testSerialize() {
+			RosterItemExchangeSerializer testling;
+			boost::shared_ptr<RosterItemExchangePayload> roster(new RosterItemExchangePayload());
+
+			RosterItemExchangePayload::Item item1;
+			item1.jid = JID("foo@bar.com");
+			item1.name = "Foo @ Bar";
+			item1.action = RosterItemExchangePayload::Add;
+			item1.groups.push_back("Group 1");
+			item1.groups.push_back("Group 2");
+			roster->addItem(item1);
+
+			RosterItemExchangePayload::Item item2;
+			item2.jid = JID("baz@blo.com");
+			item2.name = "Baz";
+			item2.action = RosterItemExchangePayload::Modify;
+			roster->addItem(item2);
+
+			std::string expectedResult = 
+				"<x xmlns=\"http://jabber.org/protocol/rosterx\">"
+					"<item action=\"add\" jid=\"foo@bar.com\" name=\"Foo @ Bar\">"
+						"<group>Group 1</group>"
+						"<group>Group 2</group>"
+					"</item>"
+					"<item action=\"modify\" jid=\"baz@blo.com\" name=\"Baz\"/>"
+				"</x>";
+
+			CPPUNIT_ASSERT_EQUAL(expectedResult, testling.serialize(roster));
+		}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(RosterItemExchangeSerializerTest);
-- 
cgit v0.10.2-6-g49f6