diff options
| -rw-r--r-- | libadhocutil/plugins.cpp | 131 | ||||
| -rw-r--r-- | libadhocutil/plugins.h | 163 | ||||
| -rw-r--r-- | libadhocutil/plugins.impl.h | 79 | ||||
| -rw-r--r-- | libadhocutil/unittests/Jamfile.jam | 10 | ||||
| -rw-r--r-- | libadhocutil/unittests/testPlugins.cpp | 110 | 
5 files changed, 493 insertions, 0 deletions
| diff --git a/libadhocutil/plugins.cpp b/libadhocutil/plugins.cpp new file mode 100644 index 0000000..7f5436f --- /dev/null +++ b/libadhocutil/plugins.cpp @@ -0,0 +1,131 @@ +#include "plugins.h" +#include <string.h> +#include <boost/multi_index_container.hpp> +#include <boost/multi_index/ordered_index.hpp> +#include "buffer.h" + +namespace std { +	bool +	operator<(const std::type_info & a, const std::type_info & b) +	{ +		return a.hash_code() < b.hash_code(); +	} + +	std::ostream & +	operator<<(std::ostream & s, const std::type_info & t) +	{ +		char * buf = __cxxabiv1::__cxa_demangle(t.name(), NULL, NULL, NULL); +		s << buf; +		free(buf); +		return s; +	} +} + +namespace AdHoc { +	static void createDefaultManager() __attribute__((constructor(101))); +	static void deleteDefaultManager() __attribute__((destructor(101))); + +	static AdHoc::PluginManager * defaultPluginManager; + +	static +	void +	createDefaultManager() +	{ +		defaultPluginManager = new PluginManager(); +	} + +	static +	void +	deleteDefaultManager() +	{ +		delete defaultPluginManager; +		defaultPluginManager = nullptr; +	} + +	Plugin::Plugin(const std::string & n, const std::string & f, int l) : +		name(n), +		filename(f), +		lineno(l) +	{ +	} + +	Plugin::~Plugin() = default; + +	NoSuchPluginException::NoSuchPluginException(const std::string & n, const std::type_info & t) : +		std::runtime_error(stringbf("No such plugin: %s of type %s", n, t)) +	{ +	} + +	DuplicatePluginException::DuplicatePluginException(PluginPtr p1, PluginPtr p2) : +		std::runtime_error(stringbf("Duplicate plugin %s for type %s at %s:%d, originally from %s:%d", +				p1->name, p1->type(), p2->filename, p2->lineno, p1->filename, p1->lineno)) +	{ +	} + +	PluginManager::PluginManager() : +		plugins(new PluginStore()) +	{ +	} + +	PluginManager::~PluginManager() +	{ +		delete plugins; +		fputs(__PRETTY_FUNCTION__, stderr); +	} + +	PluginManager * +	PluginManager::getDefault() +	{ +		return defaultPluginManager; +	} + +	void +	PluginManager::add(PluginPtr p) +	{ +		auto prev = plugins->insert(p); +		if (!prev.second) { +			throw DuplicatePluginException(*prev.first, p); +		} +	} + +	void +	PluginManager::remove(const std::string & n, const std::type_info & t) +	{ +		auto r = plugins->get<2>().equal_range(boost::make_tuple(n, std::cref(t))); +		plugins->get<2>().erase(r.first, r.second); +	} + +	PluginPtr +	PluginManager::get(const std::string & n, const std::type_info & t) const +	{ +		auto r = plugins->get<2>().equal_range(boost::make_tuple(n, std::cref(t))); +		if (r.first != r.second) { +			return (*r.first); +		} +		throw NoSuchPluginException(n, t); +	} + +	std::set<PluginPtr> +	PluginManager::getAll() const +	{ +		std::set<PluginPtr> all; +		for(const auto & p : *plugins) { +			all.insert(p); +		} +		return all; +	} + +	std::set<PluginPtr> +	PluginManager::getAll(const std::type_info & t) const +	{ +		auto r = plugins->get<1>().equal_range(t); +		return std::set<PluginPtr>(r.first, r.second); +	} + +	size_t +	PluginManager::count() const +	{ +		return plugins->size(); +	} +} + diff --git a/libadhocutil/plugins.h b/libadhocutil/plugins.h new file mode 100644 index 0000000..69dd05c --- /dev/null +++ b/libadhocutil/plugins.h @@ -0,0 +1,163 @@ +#ifndef ADHOCUTIL_PLUGINS_H +#define ADHOCUTIL_PLUGINS_H + +#include <boost/shared_ptr.hpp> +#include <boost/multi_index_container_fwd.hpp> +#include <boost/multi_index/ordered_index_fwd.hpp> +#include <boost/multi_index/member.hpp> +#include <boost/multi_index/mem_fun.hpp> +#include <boost/multi_index/composite_key.hpp> +#include <typeinfo> +#include <set> +#include <algorithm> +#include "visibility.h" + +namespace std { +	DLL_PUBLIC +	std::ostream & +	operator<<(std::ostream & s, const std::type_info & t); +} + +namespace AdHoc { +	/// Thrown when no matching plugin can be found. +	class NoSuchPluginException : public std::runtime_error { +		public: +			/// Constructor taking name and type of plugin requested. +			NoSuchPluginException(const std::string &, const std::type_info &); +	}; + +	/// Base class for untyped plugins. +	class DLL_PUBLIC Plugin { +		public: +			/// Constructor taking name, filename and line of install. +			Plugin(const std::string &, const std::string &, int); +			virtual ~Plugin(); + +			/// Get the plugin type from the subclass. +			virtual const std::type_info & type() const = 0; + +			/// The name the plugin was installed with. +			const std::string name; +			/// The filename the plugin was installed in. +			const std::string filename; +			/// The line of file the plugin was installed in. +			const int lineno; +	}; +	typedef boost::shared_ptr<const Plugin> PluginPtr; + +	/// Thrown when a plugin with the same name and base is loaded into a manager. +	class DuplicatePluginException : public std::runtime_error { +		public: +			/// Constructor taking the original and offending plugin. +			DuplicatePluginException(PluginPtr p1, PluginPtr p2); +	}; + +	template <typename T> +	/// Typed plugin and handle to implementation. +	class DLL_PUBLIC PluginOf : public Plugin { +		public: +			/// Constructor taking an instance and name, filename and line of install for Plugin. +			PluginOf(const T * t, const std::string & n, const std::string & f, int l); +			~PluginOf(); + +			/// Get the type of this plugin. +			const std::type_info & type() const override; +			/// Get the implementation of this plugin. +			const T * implementation() const; + +		private: +			const T * impl; +	}; + +	/// Container for loaded plugins. +	class DLL_PUBLIC PluginManager { +		public: +			PluginManager(); +			virtual ~PluginManager(); + +			/// Install a plugin. +			void add(PluginPtr); +			/// Uninstall a plugin. +			void remove(const std::string &, const std::type_info &); +			/// Get a specific plugin. +			PluginPtr get(const std::string &, const std::type_info &) const; +			/// Get all plugins. +			std::set<PluginPtr> getAll() const; +			/// Get all plugins of a specific type. +			std::set<PluginPtr> getAll(const std::type_info &) const; + +			/** +			 * Install a plugin. +			 * @param i Implementation instance. +			 * @param n Name of plugin. +			 * @param f Filename of plugin. +			 * @param l Line number. +			 */ +			template<typename T> void add(const T * i, const std::string & n, const std::string & f, int l); + +			/** +			 * Uninstall a plugin. +			 * @param n Name of plugin. +			 */ +			template<typename T> void remove(const std::string & n); + +			/** +			 * Get a specific plugin. +			 * @param n Name of plugin. +			 */ +			template<typename T> boost::shared_ptr<const PluginOf<T>> get(const std::string & n) const; + +			/** +			 * Get the implementation from specific plugin. +			 * @param n Name of plugin. +			 */ +			template<typename T> const T * getImplementation(const std::string & n) const; + +			/** +			 * Get all plugins of a given time. +			 */ +			template<typename T> std::set<boost::shared_ptr<const PluginOf<T>>> getAll() const; + +			/** +			 * The number of installed plugins. +			 */ +			size_t count() const; + +			/** +			 * Get the default plugin manager instance. +			 */ +			static PluginManager * getDefault(); + +		private: +			typedef boost::multi_index_container<PluginPtr, +							boost::multi_index::indexed_by< +								boost::multi_index::ordered_non_unique<boost::multi_index::member<Plugin, const std::string, &Plugin::name>>, +								boost::multi_index::ordered_non_unique<boost::multi_index::const_mem_fun<Plugin, const std::type_info &, &Plugin::type>>, +								boost::multi_index::ordered_unique< +									boost::multi_index::composite_key< +										Plugin, +										boost::multi_index::member<Plugin, const std::string, &Plugin::name>, +										boost::multi_index::const_mem_fun<Plugin, const std::type_info &, &Plugin::type> +										>> +								>> PluginStore; + +			PluginStore * plugins; +	}; +} + +#define NAMEDPLUGIN(Name, Implementation, Base) \ +	namespace { \ +		static void InstallPlugin() __attribute__((constructor(102))); \ +		void InstallPlugin() { \ +			::AdHoc::PluginManager::getDefault()->add<Base>(new Implementation(), Name, __FILE__, __LINE__); \ +		} \ +		static void UninstallPlugin() __attribute__((destructor(102))); \ +		void UninstallPlugin() { \ +			::AdHoc::PluginManager::getDefault()->remove<Base>(Name); \ +		} \ +	} +#define PLUGIN(Implementation, Base) \ +	NAMEDPLUGIN(#Implementation, Implementation, Base) + +#endif + diff --git a/libadhocutil/plugins.impl.h b/libadhocutil/plugins.impl.h new file mode 100644 index 0000000..1ece9a3 --- /dev/null +++ b/libadhocutil/plugins.impl.h @@ -0,0 +1,79 @@ +#ifndef ADHOCUTIL_PLUGINS_IMPL_H +#define ADHOCUTIL_PLUGINS_IMPL_H + +#include "plugins.h" + +namespace AdHoc { +	template <typename T> +	PluginOf<T>::PluginOf(const T * t, const std::string & n, const std::string & f, int l) : +		Plugin(n, f, l), +		impl(t) +	{ +	} + +	template <typename T> +	PluginOf<T>::~PluginOf() +	{ +		delete impl; +	} + +	/// Get the type of this plugin. +	template <typename T> +	const std::type_info & +	PluginOf<T>::type() const +	{ +		return typeid(*impl); +	} + +	/// Get the implementation of this plugin. +	template <typename T> +	const T * +	PluginOf<T>::implementation() const +	{ +		return impl; +	} + +	template <typename T> +	void +	PluginManager::add(const T * i, const std::string & n, const std::string & f, int l) +	{ +		add(PluginPtr(new PluginOf<T>(i, n, f, l))); +	} + +	template <typename T> +	void +	PluginManager::remove(const std::string & n) +	{ +		remove(n, typeid(T)); +	} + +	template <typename T> +	boost::shared_ptr<const PluginOf<T>> +	PluginManager::get(const std::string & n) const +	{ +		return boost::dynamic_pointer_cast<const PluginOf<T>>(get(n, typeid(T))); +	} + +	template <typename T> +	const T * +	PluginManager::getImplementation(const std::string & n) const +	{ +		return get<T>(n)->implementation(); +	} + +	template <typename T> +	std::set<boost::shared_ptr<const PluginOf<T>>> +	PluginManager::getAll() const +	{ +		std::set<boost::shared_ptr<const PluginOf<T>>> all; +		for(const auto & p : getAll(typeid(T))) { +			if (auto tp = boost::dynamic_pointer_cast<const PluginOf<T>>(p)) { +				all.insert(tp); +			} +		} +		return all; +	} +} + +#endif + diff --git a/libadhocutil/unittests/Jamfile.jam b/libadhocutil/unittests/Jamfile.jam index 8275cbb..77aede4 100644 --- a/libadhocutil/unittests/Jamfile.jam +++ b/libadhocutil/unittests/Jamfile.jam @@ -119,3 +119,13 @@ run  	testCache  	; +run +	testPlugins.cpp +	: : : +	<define>BOOST_TEST_DYN_LINK +	<library>..//adhocutil +	<library>boost_utf +	: +	testPlugins +	; + diff --git a/libadhocutil/unittests/testPlugins.cpp b/libadhocutil/unittests/testPlugins.cpp new file mode 100644 index 0000000..130d577 --- /dev/null +++ b/libadhocutil/unittests/testPlugins.cpp @@ -0,0 +1,110 @@ +#define BOOST_TEST_MODULE Plugins +#include <boost/test/unit_test.hpp> + +#include "plugins.h" +#include "plugins.impl.h" + +using namespace AdHoc; + +class BaseThing { }; + +class ImplOfThing : public BaseThing { }; +class OtherImplOfThing : public BaseThing { }; + +class OtherBase { }; + +class OtherImpl : public OtherBase { }; + +PLUGIN(ImplOfThing, BaseThing); + +BOOST_AUTO_TEST_CASE( ready ) +{ +	BOOST_REQUIRE(PluginManager::getDefault()); +} + +BOOST_AUTO_TEST_CASE( registered ) +{ +	BOOST_REQUIRE_EQUAL(1, PluginManager::getDefault()->count()); +} + +BOOST_AUTO_TEST_CASE( get ) +{ +	auto implOfThingPlugin = PluginManager::getDefault()->get<BaseThing>("ImplOfThing"); +	BOOST_REQUIRE(implOfThingPlugin != nullptr); +	auto implOfThing = implOfThingPlugin->implementation(); +	BOOST_REQUIRE(implOfThing != nullptr); +	BOOST_REQUIRE_EQUAL(typeid(BaseThing), typeid(*implOfThing)); +	auto implOfThingDirect = PluginManager::getDefault()->getImplementation<BaseThing>("ImplOfThing"); +	BOOST_REQUIRE_EQUAL(implOfThing, implOfThingDirect); +} + +BOOST_AUTO_TEST_CASE( getAll ) +{ +	auto all = PluginManager::getDefault()->getAll(); +	BOOST_REQUIRE_EQUAL(1, all.size()); +	auto allOf = PluginManager::getDefault()->getAll<BaseThing>(); +	BOOST_REQUIRE_EQUAL(1, allOf.size()); +} + +BOOST_AUTO_TEST_CASE( addManual ) +{ +	auto o1 = PluginManager::getDefault()->get<BaseThing>("ImplOfThing"); +	PluginManager::getDefault()->add(PluginPtr(new PluginOf<BaseThing>(new ImplOfThing(), "custom1", __FILE__, __LINE__))); +	BOOST_REQUIRE_EQUAL(2, PluginManager::getDefault()->count()); +	auto c1 = PluginManager::getDefault()->get<BaseThing>("custom1"); +	PluginManager::getDefault()->add<BaseThing>(new ImplOfThing(), "custom2", __FILE__, __LINE__); +	BOOST_REQUIRE_EQUAL(3, PluginManager::getDefault()->count()); +	auto c2 = PluginManager::getDefault()->get<BaseThing>("custom2"); +	auto o2 = PluginManager::getDefault()->get<BaseThing>("ImplOfThing"); +	BOOST_REQUIRE(o1); +	BOOST_REQUIRE(c1); +	BOOST_REQUIRE(c2); +	BOOST_REQUIRE(o2); +	BOOST_REQUIRE_EQUAL(o1, o2); +	BOOST_REQUIRE(c1 != o1); +	BOOST_REQUIRE(c2 != o1); +} + +BOOST_AUTO_TEST_CASE( removeManual ) +{ +	BOOST_REQUIRE_EQUAL(3, PluginManager::getDefault()->count()); +	PluginManager::getDefault()->remove<BaseThing>("custom1"); +	BOOST_REQUIRE_EQUAL(2, PluginManager::getDefault()->count()); +	BOOST_REQUIRE_THROW(PluginManager::getDefault()->get<BaseThing>("custom1"), NoSuchPluginException); +	BOOST_REQUIRE(PluginManager::getDefault()->get<BaseThing>("custom2")); +	BOOST_REQUIRE(PluginManager::getDefault()->get<BaseThing>("ImplOfThing")); +	PluginManager::getDefault()->remove<BaseThing>("custom2"); +	BOOST_REQUIRE_EQUAL(1, PluginManager::getDefault()->count()); +	BOOST_REQUIRE_THROW(PluginManager::getDefault()->get<BaseThing>("custom2"), NoSuchPluginException); +	BOOST_REQUIRE(PluginManager::getDefault()->get<BaseThing>("ImplOfThing")); +} + +BOOST_AUTO_TEST_CASE( nameAndTypeClash ) +{ +	// Same name, different type +	PluginManager::getDefault()->add<OtherBase>(new OtherImpl(), "ImplOfThing", __FILE__, __LINE__); +	// Different name, same type +	PluginManager::getDefault()->add<BaseThing>(new ImplOfThing(), "Different", __FILE__, __LINE__); +	// Same name, same thing, should error +	BOOST_REQUIRE_THROW(PluginManager::getDefault()->add<BaseThing>(new OtherImplOfThing(), "ImplOfThing", __FILE__, __LINE__), DuplicatePluginException); +	PluginManager::getDefault()->remove<OtherBase>("ImplOfThing"); +	PluginManager::getDefault()->remove<BaseThing>("Different"); +} + +BOOST_AUTO_TEST_CASE( otherTypes ) +{ +	PluginManager::getDefault()->add<OtherBase>(new OtherImpl(), "ImplOfThing", __FILE__, __LINE__); +	BOOST_REQUIRE_EQUAL(2, PluginManager::getDefault()->count()); +	BOOST_REQUIRE_EQUAL(2, PluginManager::getDefault()->getAll().size()); +	BOOST_REQUIRE_EQUAL(1, PluginManager::getDefault()->getAll<BaseThing>().size()); +	BOOST_REQUIRE_EQUAL(1, PluginManager::getDefault()->getAll<OtherBase>().size()); +	PluginPtr p1 = PluginManager::getDefault()->get<BaseThing>("ImplOfThing"); +	PluginPtr p2 = PluginManager::getDefault()->get<OtherBase>("ImplOfThing"); +	BOOST_REQUIRE(p1); +	BOOST_REQUIRE(p2); +	BOOST_REQUIRE(p1 != p2); +	PluginManager::getDefault()->remove<OtherBase>("ImplOfThing"); +	BOOST_REQUIRE_EQUAL(1, PluginManager::getDefault()->count()); +	BOOST_REQUIRE(PluginManager::getDefault()->get<BaseThing>("ImplOfThing")); +} + | 
