From 3cdfcb6fcc749f460f1239039bb11b1de078f8d4 Mon Sep 17 00:00:00 2001
From: randomdan <randomdan@localhost>
Date: Thu, 11 Sep 2014 18:45:02 +0000
Subject: Allow serializers to use their own native types. Allow .ice metadata
 to specify typesafe conversion functions between serializer types and model
 types

---
 slicer/slicer/modelParts.h            |  86 ++++++++++++++++++-----
 slicer/slicer/parser.cpp              | 124 ++++++++++++++++++++++++++++++++--
 slicer/slicer/parser.h                |  12 ++++
 slicer/test/Jamfile.jam               |   1 +
 slicer/test/do-slicer.cpp             |  28 ++++++++
 slicer/test/initial/conv-datetime.xml |   4 ++
 slicer/test/run-slicer.cpp            |  45 ++++++++++++
 slicer/test/types.ice                 |  15 ++++
 8 files changed, 293 insertions(+), 22 deletions(-)
 create mode 100644 slicer/test/initial/conv-datetime.xml

diff --git a/slicer/slicer/modelParts.h b/slicer/slicer/modelParts.h
index 6302b9f..41b0617 100644
--- a/slicer/slicer/modelParts.h
+++ b/slicer/slicer/modelParts.h
@@ -23,29 +23,55 @@ namespace Slicer {
 			UnknownType(const std::string & n);
 	};
 
-	class ValueTarget : public IceUtil::Shared {
+	template <typename T>
+	class TValueTarget {
 		public:
-			virtual void get(const bool &) const = 0;
-			virtual void get(const Ice::Byte &) const = 0;
-			virtual void get(const Ice::Short &) const = 0;
-			virtual void get(const Ice::Int &) const = 0;
-			virtual void get(const Ice::Long &) const = 0;
-			virtual void get(const Ice::Float &) const = 0;
-			virtual void get(const Ice::Double &) const = 0;
-			virtual void get(const std::string &) const = 0;
+			virtual void get(const T &) const = 0;
+	};
+	class ValueTarget : public IceUtil::Shared,
+			public TValueTarget<bool>,
+			public TValueTarget<Ice::Byte>,
+			public TValueTarget<Ice::Short>,
+			public TValueTarget<Ice::Int>,
+			public TValueTarget<Ice::Long>,
+			public TValueTarget<Ice::Float>,
+			public TValueTarget<Ice::Double>,
+			public TValueTarget<std::string> {
+		public:
+			using TValueTarget<bool>::get;
+			using TValueTarget<Ice::Byte>::get;
+			using TValueTarget<Ice::Short>::get;
+			using TValueTarget<Ice::Int>::get;
+			using TValueTarget<Ice::Long>::get;
+			using TValueTarget<Ice::Float>::get;
+			using TValueTarget<Ice::Double>::get;
+			using TValueTarget<std::string>::get;
 	};
 	typedef IceUtil::Handle<ValueTarget> ValueTargetPtr;
 
-	class ValueSource : public IceUtil::Shared {
+	template <typename T>
+	class TValueSource {
+		public:
+			virtual void set(T &) const = 0;
+	};
+	class ValueSource : public IceUtil::Shared,
+			public TValueSource<bool>,
+			public TValueSource<Ice::Byte>,
+			public TValueSource<Ice::Short>,
+			public TValueSource<Ice::Int>,
+			public TValueSource<Ice::Long>,
+			public TValueSource<Ice::Float>,
+			public TValueSource<Ice::Double>,
+			public TValueSource<std::string> {
 		public:
-			virtual void set(bool &) const = 0;
-			virtual void set(Ice::Byte &) const = 0;
-			virtual void set(Ice::Short &) const = 0;
-			virtual void set(Ice::Int &) const = 0;
-			virtual void set(Ice::Long &) const = 0;
-			virtual void set(Ice::Float &) const = 0;
-			virtual void set(Ice::Double &) const = 0;
-			virtual void set(std::string &) const = 0;
+			using TValueSource<bool>::set;
+			using TValueSource<Ice::Byte>::set;
+			using TValueSource<Ice::Short>::set;
+			using TValueSource<Ice::Int>::set;
+			using TValueSource<Ice::Long>::set;
+			using TValueSource<Ice::Float>::set;
+			using TValueSource<Ice::Double>::set;
+			using TValueSource<std::string>::set;
 	};
 	typedef IceUtil::Handle<ValueSource> ValueSourcePtr;
 
@@ -108,6 +134,30 @@ namespace Slicer {
 			T & Member;
 	};
 
+	template<typename T, typename M, T M::* MV>
+	class ModelPartForConverted : public ModelPart {
+		public:
+			typedef T element_type;
+
+			ModelPartForConverted(T & h) :
+				Member(h)
+			{
+			}
+			ModelPartForConverted(T * h) :
+				Member(*h)
+			{
+			}
+			virtual void OnEachChild(const ChildHandler &) { }
+			virtual ModelPartPtr GetChild(const std::string &) override { return NULL; }
+			virtual void SetValue(ValueSourcePtr s) override;
+			virtual void GetValue(ValueTargetPtr s) override;
+			virtual bool HasValue() const override { return true; }
+			virtual ModelPartType GetType() const { return mpt_Simple; }
+
+		private:
+			T & Member;
+	};
+
 	template<typename T>
 	class ModelPartForOptional : public ModelPart {
 		public:
diff --git a/slicer/slicer/parser.cpp b/slicer/slicer/parser.cpp
index c2f2df7..6c65c1f 100644
--- a/slicer/slicer/parser.cpp
+++ b/slicer/slicer/parser.cpp
@@ -3,6 +3,9 @@
 #include <Slice/Preprocessor.h>
 #include <boost/foreach.hpp>
 #include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/replace.hpp>
 #include <Slice/CPlusPlusUtil.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/filesystem/convenience.hpp>
@@ -16,6 +19,62 @@ namespace Slicer {
 	{
 	}
 
+	void
+	Slicer::defineConversions(Slice::DataMemberPtr dm) const
+	{
+		auto type = dm->type();
+		auto c = Slice::ContainedPtr::dynamicCast(dm->container());
+		auto conversions = getConversions(dm);
+		BOOST_FOREACH(const auto & conversion, conversions) {
+			fprintf(cpp, "%s %s(const %s &);\n",
+					conversion.ExchangeType.c_str(),
+					conversion.ConvertToExchangeFunc.c_str(),
+					Slice::typeToString(type).c_str());
+			fprintf(cpp, "%s %s(const %s &);\n\n",
+					Slice::typeToString(type).c_str(),
+					conversion.ConvertToModelFunc.c_str(),
+					conversion.ExchangeType.c_str());
+		}
+		if (!conversions.empty()) {
+			fprintf(cpp, "template<>\nvoid\n");
+			fprintf(cpp, "ModelPartForConverted< %s, %s::%s, &%s::%s::%s >::SetValue(ValueSourcePtr vsp)\n",
+					Slice::typeToString(type).c_str(),
+					modulePath().c_str(), c->name().c_str(),
+					modulePath().c_str(), c->name().c_str(), dm->name().c_str());
+			fprintf(cpp, "{\n");
+
+			BOOST_FOREACH(const auto & conversion, conversions) {
+				fprintf(cpp, "\tif (auto vspt = dynamic_cast<TValueSource< %s > *>(vsp.get())) {\n",
+						conversion.ExchangeType.c_str());
+				fprintf(cpp, "\t\t%s tmp;\n",
+						conversion.ExchangeType.c_str());
+				fprintf(cpp, "\t\tvspt->set(tmp);\n");
+				fprintf(cpp, "\t\tMember = %s(tmp);\n",
+						conversion.ConvertToModelFunc.c_str());
+				fprintf(cpp, "\t\treturn;\n");
+				fprintf(cpp, "\t}\n");
+			}
+			fprintf(cpp, "}\n\n");
+
+			fprintf(cpp, "template<>\nvoid\n");
+			fprintf(cpp, "ModelPartForConverted< %s, %s::%s, &%s::%s::%s >::GetValue(ValueTargetPtr vtp)\n",
+					Slice::typeToString(type).c_str(),
+					modulePath().c_str(), c->name().c_str(),
+					modulePath().c_str(), c->name().c_str(), dm->name().c_str());
+			fprintf(cpp, "{\n");
+
+			BOOST_FOREACH(const auto & conversion, conversions) {
+				fprintf(cpp, "\tif (auto vtpt = dynamic_cast<TValueTarget< %s > *>(vtp.get())) {\n",
+						conversion.ExchangeType.c_str());
+				fprintf(cpp, "\t\tvtpt->get(%s(Member));\n",
+						conversion.ConvertToExchangeFunc.c_str());
+				fprintf(cpp, "\t\treturn;\n");
+				fprintf(cpp, "\t}\n");
+			}
+			fprintf(cpp, "}\n\n");
+		}
+	}
+
 	bool
 	Slicer::visitUnitStart(const Slice::UnitPtr & u)
 	{
@@ -40,6 +99,16 @@ namespace Slicer {
 	{
 		fprintf(cpp, "// Begin module %s\n\n", m->name().c_str());
 		modules.push_back(m);
+		BOOST_FOREACH(const auto & c, m->structs()) {
+			BOOST_FOREACH(const auto & dm, c->dataMembers()) {
+				defineConversions(dm);
+			}
+		}
+		BOOST_FOREACH(const auto & c, m->classes()) {
+			BOOST_FOREACH(const auto & dm, c->dataMembers()) {
+				defineConversions(dm);
+			}
+		}
 		return true;
 	}
 
@@ -116,6 +185,7 @@ namespace Slicer {
 				t = Slice::ClassDefPtr::dynamicCast(dm->container())->declaration();
 			}
 			auto name = metaDataValue("slicer:name:", dm->getMetaData());
+			auto conversions = metaDataValues("slicer:conversion:", dm->getMetaData());
 			fprintf(cpp, "\t\tnew ");
 			auto type = dm->type();
 			createNewModelPartPtrFor(t);
@@ -128,13 +198,21 @@ namespace Slicer {
 			if (dm->optional()) {
 				fprintf(cpp, "ModelPartForOptional< ");
 			}
-			createNewModelPartPtrFor(type);
-			fprintf(cpp, "< %s",
-					Slice::typeToString(type).c_str());
+			if (!conversions.empty()) {
+				fprintf(cpp, "ModelPartForConverted< %s, %s::%s, &%s::%s::%s >",
+						Slice::typeToString(type).c_str(),
+						modulePath().c_str(), c->name().c_str(),
+						modulePath().c_str(), c->name().c_str(), dm->name().c_str());
+			}
+			else {
+				createNewModelPartPtrFor(type);
+				fprintf(cpp, "< %s >",
+						Slice::typeToString(type).c_str());
+			}
 			if (dm->optional()) {
 				fprintf(cpp, " > ");
 			}
-			fprintf(cpp, " > >(\"%s\"),\n",
+			fprintf(cpp, " >(\"%s\"),\n",
 					name ? name->c_str() : dm->name().c_str());
 		}
 	}
@@ -269,6 +347,44 @@ namespace Slicer {
 		return boost::optional<std::string>();
 	}
 
+	std::list<std::string>
+	Slicer::metaDataValues(const std::string & prefix, const std::list<std::string> & metadata)
+	{
+		std::list<std::string> mds;
+		BOOST_FOREACH (const auto & md, metadata) {
+			if (boost::algorithm::starts_with(md, prefix)) {
+				mds.push_back(md.substr(prefix.length()));
+			}
+		}
+		return mds;
+	}
+
+	std::vector<std::string>
+	Slicer::metaDataSplit(const std::string & metadata)
+	{
+		std::vector<std::string> parts;
+		boost::algorithm::split(parts, metadata, boost::algorithm::is_any_of(":"), boost::algorithm::token_compress_off); 
+		return parts;	
+	}
+
+	std::vector<Slicer::ConversionSpec>
+	Slicer::getConversions(Slice::DataMemberPtr dm)
+	{
+		std::vector<ConversionSpec> rtn;
+		auto conversions = metaDataValues("slicer:conversion:", dm->getMetaData());
+		BOOST_FOREACH(const auto & conversion, conversions) {
+			auto split = metaDataSplit(conversion);
+			if (split.size() != 3) {
+				throw std::runtime_error("conversion needs 3 parts type:toModelFunc:toExchangeFunc");
+			}
+			BOOST_FOREACH(auto & p, split) {
+				boost::algorithm::replace_all(p, ".", "::");
+			}
+			rtn.push_back(ConversionSpec({split[0], split[1], split[2]}));
+		}
+		return rtn;
+	}
+
 	void
 	Slicer::Apply(const boost::filesystem::path & ice, const boost::filesystem::path & cpp)
 	{
diff --git a/slicer/slicer/parser.h b/slicer/slicer/parser.h
index 816738d..64d961f 100644
--- a/slicer/slicer/parser.h
+++ b/slicer/slicer/parser.h
@@ -10,6 +10,13 @@ namespace Slicer {
 
 	class Slicer : public Slice::ParserVisitor {
 		public:
+			class ConversionSpec {
+				public:
+					std::string ExchangeType;
+					std::string ConvertToModelFunc;
+					std::string ConvertToExchangeFunc;
+			};
+
 			Slicer(FILE * c);
 
 			static void Apply(const boost::filesystem::path & ice, const boost::filesystem::path & cpp);
@@ -38,7 +45,12 @@ namespace Slicer {
 
 			std::string modulePath() const;
 
+			void defineConversions(Slice::DataMemberPtr dm) const;
+
 			static boost::optional<std::string> metaDataValue(const std::string & prefix, const std::list<std::string> & metadata);
+			static std::list<std::string> metaDataValues(const std::string & prefix, const std::list<std::string> & metadata);
+			static std::vector<std::string> metaDataSplit(const std::string & metadata);
+			static std::vector<ConversionSpec> getConversions(Slice::DataMemberPtr);
 
 			FILE * cpp;
 			std::vector<Slice::ModulePtr> modules;
diff --git a/slicer/test/Jamfile.jam b/slicer/test/Jamfile.jam
index 1426704..4a149a8 100644
--- a/slicer/test/Jamfile.jam
+++ b/slicer/test/Jamfile.jam
@@ -34,6 +34,7 @@ unit-test do-slicer :
 	common
 	do-slicer.cpp
 	:
+	<linkflags>-rdynamic
 	<library>dl
 	<include>..
 	<library>pthread
diff --git a/slicer/test/do-slicer.cpp b/slicer/test/do-slicer.cpp
index 30c5cfb..7607ff2 100644
--- a/slicer/test/do-slicer.cpp
+++ b/slicer/test/do-slicer.cpp
@@ -9,6 +9,34 @@
 
 namespace fs = boost::filesystem;
 
+namespace Slicer {
+	// These are the conversion helpers defined used in types.ice, they're not called in this test, but
+	// need to exist for the dynamic load to complete
+	boost::posix_time::ptime
+	dateTimeToPTime(const ::TestModule::DateTime &)
+	{
+		throw std::runtime_error("Not implemented");
+	}
+
+	::TestModule::DateTime
+	ptimeToDateTime(const boost::posix_time::ptime &)
+	{
+		throw std::runtime_error("Not implemented");
+	}
+
+	std::string
+	dateTimeToString(const ::TestModule::DateTime &)
+	{
+		throw std::runtime_error("Not implemented");
+	}
+
+	::TestModule::DateTime
+	stringToDateTime(const std::string &)
+	{
+		throw std::runtime_error("Not implemented");
+	}
+}
+
 int
 main(int, char ** argv)
 {
diff --git a/slicer/test/initial/conv-datetime.xml b/slicer/test/initial/conv-datetime.xml
new file mode 100644
index 0000000..b4678b4
--- /dev/null
+++ b/slicer/test/initial/conv-datetime.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<DateTimeContainer>
+  <dt>2014-Dec-25 12:13:14</dt>
+</DateTimeContainer>
diff --git a/slicer/test/run-slicer.cpp b/slicer/test/run-slicer.cpp
index 0044c64..73d5be8 100644
--- a/slicer/test/run-slicer.cpp
+++ b/slicer/test/run-slicer.cpp
@@ -6,6 +6,7 @@
 #include <json/serializer.h>
 #include <boost/filesystem/convenience.hpp>
 #include <boost/filesystem/operations.hpp>
+#include <boost/numeric/conversion/cast.hpp>
 #include <boost/format.hpp>
 #include <boost/function.hpp>
 #include <boost/assert.hpp>
@@ -15,6 +16,49 @@
 #include "helpers.h"
 
 namespace fs = boost::filesystem;
+#define SHORT(x) boost::numeric_cast< ::Ice::Short >(x)
+
+namespace Slicer {
+	boost::posix_time::ptime
+	dateTimeToPTime(const ::TestModule::DateTime &)
+	{
+		throw std::runtime_error("Not implemented");
+	}
+
+	::TestModule::DateTime
+	ptimeToDateTime(const boost::posix_time::ptime &)
+	{
+		throw std::runtime_error("Not implemented");
+	}
+
+	std::string
+	dateTimeToString(const ::TestModule::DateTime & in)
+	{
+		char buf[BUFSIZ];
+		struct tm tm({ in.second, in.minute, in.hour, in.day, in.month, in.year, 0, 0, 0
+#ifdef _BSD_SOURCE
+				, 0, 0
+#endif
+				});
+		mktime(&tm);
+		auto len = strftime(buf, BUFSIZ, "%Y-%b-%d %H:%M:%S", &tm);
+		return std::string(buf, len);
+	}
+
+	::TestModule::DateTime
+	stringToDateTime(const std::string & in)
+	{
+		struct tm tm;
+		memset(&tm, 0, sizeof(struct tm));
+		auto end = strptime(in.c_str(), "%Y-%b-%d %H:%M:%S", &tm);
+		if (!end || *end) {
+			throw std::runtime_error("Invalid date string: " + in);
+		}
+		return ::TestModule::DateTime({
+				SHORT(tm.tm_year), SHORT(tm.tm_mon), SHORT(tm.tm_mday),
+				SHORT(tm.tm_hour), SHORT(tm.tm_min), SHORT(tm.tm_sec)});
+	}
+}
 
 template<typename T, typename SerializerIn>
 void
@@ -200,6 +244,7 @@ main(int, char ** argv)
 	verifyByFile<TestModule::Optionals, Slicer::XmlFile>(root, tmpf, "optionals-areset.xml", checkOptionals_areset);
 	verifyByFile<TestModule::InheritanceCont, Slicer::XmlFile>(root, tmpf, "inherit-a.xml");
 	verifyByFile<TestModule::InheritanceCont, Slicer::XmlFile>(root, tmpf, "inherit-b.xml");
+	verifyByFile<TestModule::DateTimeContainer, Slicer::XmlFile>(root, tmpf, "conv-datetime.xml");
 	verifyByFile<TestModule::BuiltIns, Slicer::JsonFile>(root, tmpf, "builtins2.json", checkBuiltIns_valuesCorrect);
 	verifyByFile<TestModule::Optionals, Slicer::JsonFile>(root, tmpf, "optionals-areset2.json", checkOptionals_areset);
 
diff --git a/slicer/test/types.ice b/slicer/test/types.ice
index d2fe1ab..1f484d3 100644
--- a/slicer/test/types.ice
+++ b/slicer/test/types.ice
@@ -1,4 +1,19 @@
+[["cpp:include:boost/date_time/posix_time/posix_time_types.hpp"]]
+
 module TestModule {
+	struct DateTime {
+		short year;
+		short month;
+		short day;
+		short hour;
+		short minute;
+		short second;
+	};
+	class DateTimeContainer {
+		[	"slicer:conversion:boost.posix_time.ptime:ptimeToDateTime:dateTimeToPTime",
+			"slicer:conversion:std.string:stringToDateTime:dateTimeToString" ]
+		DateTime dt;
+	};
 	class BuiltIns {
 		bool mbool;
 		byte mbyte;
-- 
cgit v1.2.3