#ifndef MATADOR_DI_HPP #define MATADOR_DI_HPP #include "matador/utils/singleton.hpp" #include #include #include #include #include #include #include #include #include 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 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 explicit transient_strategy(Args &&...args) { creator_ = [args..., this]() { instances_.push_back(std::make_unique(args...)); return instances_.back().get(); }; } void *acquire() override { return creator_(); } private: std::function creator_{}; std::vector> 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 singleton_strategy : public strategy { public: template explicit singleton_strategy(Args &&...args) { creator_ = [args..., this]() { if (!instance_) { instance_ = std::make_unique(args...); } return instance_.get(); }; } void *acquire() override { return creator_(); } private: std::function creator_{}; std::unique_ptr instance_; }; template class singleton_per_thread_strategy : public strategy { public: template explicit singleton_per_thread_strategy(Args &&...args) { creator_ = [args..., this]() { thread_local auto instance = std::make_unique(args...); std::lock_guard 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 creator_{}; std::mutex instance_mutex_; std::unordered_map> instance_map_; }; /** * @brief Provides a specific instance on injection * * @tparam T */ template 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 T* get() const { return static_cast(strategy_->acquire()); } protected: void initialize_strategy(std::unique_ptr &&strategy) { strategy_ = std::move(strategy); } private: std::unique_ptr strategy_; }; template class proxy : public proxy_base { public: template> * = nullptr> void to( Args &&...args) { initialize_strategy(std::make_unique>(std::forward(args)...)); } template void to_instance(T &&obj) { initialize_strategy(std::make_unique>(obj)); } template> * = nullptr> void to_singleton(Args &&...args) { initialize_strategy(std::make_unique>(std::forward(args)...)); } template> * = nullptr> void to_singleton_per_thread(Args &&...args) { initialize_strategy(std::make_unique>(std::forward(args)...)); } }; class module { private: using t_type_proxy_map = std::unordered_map>; public: void clear() { default_map_.clear(); module_map_.clear(); } template std::shared_ptr> bind() { return bind(default_map_); } template std::shared_ptr> 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->second); } template I* resolve() { return resolve(default_map_); } template 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->second); } private: template std::shared_ptr> bind(t_type_proxy_map &type_proxy_map) { auto di_proxy_ptr = std::make_shared>(); auto i = type_proxy_map.insert(std::make_pair(std::type_index(typeid(I)), di_proxy_ptr)); return std::static_pointer_cast>(i.first->second); } template 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(); } private: t_type_proxy_map default_map_; std::unordered_map module_map_ {}; }; class repository : public utils::singleton { public: void clear() { module_.clear(); } void install(const std::function& builder) { module_.clear(); builder(module_); } void append(const std::function& builder) { builder(module_); } template I* resolve() { return module_.resolve(); } template I* resolve(const std::string &name) { return module_.resolve(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 inject { public: inject() : obj(repository::instance().resolve()) {} explicit inject(const std::string &name) : obj(repository::instance().resolve(name)) {} explicit inject(module &m) : obj(m.resolve()) {} inject(module &m, const std::string &name) : obj(m.resolve(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& builder) { repository::instance().clear(); repository::instance().install(builder); } inline void append_module(const std::function& builder) { repository::instance().append(builder); } } #endif //MATADOR_DI_HPP