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")); +} + |