query/include/matador/utils/di.hpp

386 lines
8.0 KiB
C++

#ifndef MATADOR_DI_HPP
#define MATADOR_DI_HPP
#include "matador/utils/singleton.hpp"
#include <memory>
#include <utility>
#include <vector>
#include <mutex>
#include <unordered_map>
#include <typeindex>
#include <thread>
#include <functional>
#include <stdexcept>
namespace matador::utils::di {
/**
* Interface for the dependency injection
* creation strategy.
*
* The injected service
*/
class strategy
{
public:
/**
* Destructor
*/
virtual ~strategy() = default;
/**
* @brief Acquires an instance
*
* Acquires an instance based on the internal strategy
* and returns an anonymous pointer to the object
*
* @return The current injected object depending on the strategy
*/
virtual void *acquire() = 0;
};
/**
* Implements the transient dependency injection
* strategy. When ever injected a new instance
* is created.
*
* @tparam T Type of the object to be injected
*/
template<class T>
class transient_strategy final : public strategy
{
public:
/**
* @brief Construct a new transient strategy object
*
* @tparam Args Type of arguments for constructor
* @param args Arguments for constructor
*/
template<typename ...Args>
explicit transient_strategy(Args &&...args) {
creator_ = [args..., this]() {
instances_.push_back(std::make_unique<T>(args...));
return instances_.back().get();
};
}
void *acquire() override {
return creator_();
}
private:
std::function<T *()> creator_{};
std::vector<std::unique_ptr<T>> instances_;
};
/**
* @brief Implements the singleton strategy
*
* The singleton strategy provides only one
* instance of the type.
*
* @tparam T Type of the object to be injected
*/
template<class T>
class singleton_strategy : public strategy
{
public:
template<typename ...Args>
explicit singleton_strategy(Args &&...args) {
creator_ = [args..., this]() {
if (!instance_) {
instance_ = std::make_unique<T>(args...);
}
return instance_.get();
};
}
void *acquire() override {
return creator_();
}
private:
std::function<T *()> creator_{};
std::unique_ptr<T> instance_;
};
template<class T>
class singleton_per_thread_strategy : public strategy
{
public:
template<typename ...Args>
explicit singleton_per_thread_strategy(Args &&...args) {
creator_ = [args..., this]() {
thread_local auto instance = std::make_unique<T>(args...);
std::lock_guard<std::mutex> lock(instance_mutex_);
auto it = instance_map_.insert({std::this_thread::get_id(), std::move(instance)}).first;
return it->second.get();
};
}
void *acquire() override {
return creator_();
}
private:
std::function<T *()> creator_{};
std::mutex instance_mutex_;
std::unordered_map<std::thread::id, std::unique_ptr<T>> instance_map_;
};
/**
* @brief Provides a specific instance on injection
*
* @tparam T
*/
template<class T>
class instance_strategy : public strategy
{
public:
explicit instance_strategy(T &&obj)
: instance_(obj) {}
void* acquire() override {
return &instance_;
}
private:
T &instance_;
};
class proxy_base
{
public:
virtual ~proxy_base() = default;
template<typename T>
T* get() const {
return static_cast<T *>(strategy_->acquire());
}
protected:
void initialize_strategy(std::unique_ptr<strategy> &&strategy) {
strategy_ = std::move(strategy);
}
private:
std::unique_ptr<strategy> strategy_;
};
template<typename I>
class proxy : public proxy_base
{
public:
template<typename T, typename ...Args, std::enable_if_t<std::is_base_of_v<I, T>> * = nullptr>
void to( Args &&...args) {
initialize_strategy(std::make_unique<transient_strategy<T>>(std::forward<Args &&>(args)...));
}
template<typename T>
void to_instance(T &&obj) {
initialize_strategy(std::make_unique<instance_strategy<T>>(obj));
}
template<typename T, typename ...Args, std::enable_if_t<std::is_base_of_v<I, T>> * = nullptr>
void to_singleton(Args &&...args) {
initialize_strategy(std::make_unique<singleton_strategy<T>>(std::forward<Args &&>(args)...));
}
template<typename T, typename ...Args, std::enable_if_t<std::is_base_of_v<I, T>> * = nullptr>
void to_singleton_per_thread(Args &&...args) {
initialize_strategy(std::make_unique<singleton_per_thread_strategy<T>>(std::forward<Args &&>(args)...));
}
};
class module
{
private:
using t_type_proxy_map = std::unordered_map<std::type_index, std::shared_ptr<proxy_base>>;
public:
void clear()
{
default_map_.clear();
module_map_.clear();
}
template<typename I>
std::shared_ptr<proxy<I>> bind() {
return bind<I>(default_map_);
}
template<typename I>
std::shared_ptr<proxy<I>> bind(const std::string &name) {
auto i = module_map_.find(name);
if (i == module_map_.end()) {
i = module_map_.insert(std::make_pair(name, t_type_proxy_map{})).first;
}
return bind<I>(i->second);
}
template<typename I>
I* resolve() {
return resolve<I>(default_map_);
}
template<typename I>
I* resolve(const std::string &name) {
auto i = module_map_.find(name);
if (i == module_map_.end()) {
throw std::logic_error("unknown name " + name);
}
return resolve<I>(i->second);
}
private:
template<typename I>
std::shared_ptr<proxy<I>> bind(t_type_proxy_map &type_proxy_map) {
auto di_proxy_ptr = std::make_shared<proxy<I>>();
auto i = type_proxy_map.insert(std::make_pair(std::type_index(typeid(I)), di_proxy_ptr));
return std::static_pointer_cast<proxy<I>>(i.first->second);
}
template<typename I>
static I* resolve(t_type_proxy_map &type_proxy_map) {
const auto i = type_proxy_map.find(std::type_index(typeid(I)));
if (i == type_proxy_map.end()) {
throw std::logic_error("unknown type");
}
return i->second->get<I>();
}
private:
t_type_proxy_map default_map_;
std::unordered_map<std::string, t_type_proxy_map> module_map_ {};
};
class repository : public utils::singleton<repository>
{
public:
void clear()
{
module_.clear();
}
void install(const std::function<void(module&)>& builder)
{
module_.clear();
builder(module_);
}
void append(const std::function<void(module&)>& builder)
{
builder(module_);
}
template<typename I>
I* resolve() {
return module_.resolve<I>();
}
template<typename I>
I* resolve(const std::string &name) {
return module_.resolve<I>(name);
}
private:
module module_;
};
/**
* @brief Resolves and provides the requested service
*
* The class inject acts a holder for the
* requested service. On construction, it
* tries to resolve the given template type
* within the global service repository.
*
* @tparam T Type of the interface
*/
template<class T>
class inject
{
public:
inject()
: obj(repository::instance().resolve<T>())
{}
explicit inject(const std::string &name)
: obj(repository::instance().resolve<T>(name))
{}
explicit inject(module &m)
: obj(m.resolve<T>())
{}
inject(module &m, const std::string &name)
: obj(m.resolve<T>(name))
{}
inject(const inject &x) : obj(x.obj) {}
inject& operator=(const inject &x) {
if (this != &x) {
obj = x.obj;
}
return *this;
}
inject(inject &&x) noexcept : obj(x.obj) {
x.obj = nullptr;
}
inject& operator=(inject &&x) noexcept {
obj = x.obj;
x.obj = nullptr;
return *this;
}
bool operator==(const inject &x) const {
return obj == x.obj;
}
bool operator!=(const inject &x) const {
return obj != x.obj;
}
T* operator->() const {
return obj;
}
T* get() const {
return obj;
}
T& operator*() const {
return *obj;
}
operator bool() const {
return obj != nullptr;
}
private:
T* obj;
};
inline void clear_module()
{
repository::instance().clear();
}
inline void install_module(const std::function<void(module&)>& builder)
{
repository::instance().clear();
repository::instance().install(builder);
}
inline void append_module(const std::function<void(module&)>& builder)
{
repository::instance().append(builder);
}
}
#endif //MATADOR_DI_HPP