summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libadhocutil/plugins.cpp131
-rw-r--r--libadhocutil/plugins.h163
-rw-r--r--libadhocutil/plugins.impl.h79
-rw-r--r--libadhocutil/unittests/Jamfile.jam10
-rw-r--r--libadhocutil/unittests/testPlugins.cpp110
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"));
+}
+