diff --git a/src/Base/CMakeLists.txt b/src/Base/CMakeLists.txt index a38b3ed60c..3159174c11 100644 --- a/src/Base/CMakeLists.txt +++ b/src/Base/CMakeLists.txt @@ -256,6 +256,7 @@ SET(FreeCADBase_CPP_SRCS Rotation.cpp RotationPyImp.cpp Sequencer.cpp + ServiceProvider.cpp SmartPtrPy.cpp Stream.cpp Swap.cpp @@ -319,6 +320,7 @@ SET(FreeCADBase_HPP_SRCS QtTools.h Reader.h Rotation.h + ServiceProvider.h Sequencer.h SmartPtrPy.h Stream.h @@ -356,7 +358,7 @@ IF (MSVC) ${FreeCADBase_SRCS} StackWalker.cpp StackWalker.h -) + ) ENDIF(MSVC) # Use external zipios++ if specified. diff --git a/src/Base/ServiceProvider.cpp b/src/Base/ServiceProvider.cpp new file mode 100644 index 0000000000..f1d583e69a --- /dev/null +++ b/src/Base/ServiceProvider.cpp @@ -0,0 +1,31 @@ + +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Kacper Donat * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include "ServiceProvider.h" + +Base::ServiceProvider& Base::ServiceProvider::get() +{ + static Base::ServiceProvider instance; + return instance; +} diff --git a/src/Base/ServiceProvider.h b/src/Base/ServiceProvider.h new file mode 100644 index 0000000000..738c56d172 --- /dev/null +++ b/src/Base/ServiceProvider.h @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Kacper Donat * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#ifndef APP_SERVICE_PROVIDER_H +#define APP_SERVICE_PROVIDER_H + +#include + +#include +#include +#include +#include +#include +#include + +namespace Base +{ + +class BaseExport ServiceProvider +{ + struct ServiceDescriptor + { + std::string name; + std::any instance; + + template + T* get() const + { + return std::any_cast(instance); + } + }; + +public: + ServiceProvider() = default; + + /** + * Returns most recent implementation of service specified as T param. + * + * @tparam T Service interface + */ + template + T* provide() const + { + if (auto it = _implementations.find(typeid(T).name()); it != _implementations.end()) { + auto descriptors = it->second; + + if (descriptors.empty()) { + return nullptr; + } + + return descriptors.front().get(); + } + + return nullptr; + } + + /** + * Returns all implementations of service specified as T param. + * + * @tparam T Service interface + */ + template + std::list all() const + { + if (auto it = _implementations.find(typeid(T).name()); it != _implementations.end()) { + auto source = it->second; + + std::list result(source.size()); + + std::transform(source.begin(), + source.end(), + result.begin(), + [](const ServiceDescriptor& descriptor) { + return descriptor.get(); + }); + + return result; + } + + return {}; + } + + /** + * Adds new implementation of service T. + * + * @tparam T Service interface + */ + template + void implement(T* contract) + { + ServiceDescriptor descriptor {typeid(T).name(), contract}; + + _implementations[typeid(T).name()].push_front(descriptor); + } + + static ServiceProvider& get(); + +private: + std::map> _implementations; +}; + +template +T* provideImplementation() +{ + return ServiceProvider::get().provide(); +} + +template +std::list provideAllImplementations() +{ + return ServiceProvider::get().all(); +} + +template +void implementContract(T* implementation) +{ + ServiceProvider::get().implement(implementation); +} + +} // namespace Base + + +#endif // APP_SERVICE_PROVIDER_H diff --git a/tests/src/Base/CMakeLists.txt b/tests/src/Base/CMakeLists.txt index 2feb4d2eb0..2404b058a4 100644 --- a/tests/src/Base/CMakeLists.txt +++ b/tests/src/Base/CMakeLists.txt @@ -16,6 +16,7 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/Quantity.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Reader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Rotation.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ServiceProvider.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Stream.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TimeInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Tools.cpp diff --git a/tests/src/Base/ServiceProvider.cpp b/tests/src/Base/ServiceProvider.cpp new file mode 100644 index 0000000000..405c8cf134 --- /dev/null +++ b/tests/src/Base/ServiceProvider.cpp @@ -0,0 +1,110 @@ +#include +#include + +class SimpleService +{ +public: + virtual ~SimpleService() = default; + virtual std::string foo() = 0; + + SimpleService() = default; + + SimpleService(const SimpleService& other) = delete; + SimpleService(SimpleService&& other) noexcept = delete; + SimpleService& operator=(const SimpleService& other) = delete; + SimpleService& operator=(SimpleService&& other) noexcept = delete; +}; + +class FirstServiceImplementation final: public SimpleService +{ +public: + std::string foo() override + { + return "first"; + } +}; + +class SecondServiceImplementation final: public SimpleService +{ +public: + std::string foo() override + { + return "second"; + } +}; + +TEST(ServiceProvider, provideEmptyImplementation) +{ + // Arrange + Base::ServiceProvider serviceProvider; + + // Act + auto implementation = serviceProvider.provide(); + + // Assert + EXPECT_EQ(implementation, nullptr); +} + +TEST(ServiceProvider, provideEmptyImplementationList) +{ + // Arrange + Base::ServiceProvider serviceProvider; + + // Act + const auto implementations = serviceProvider.all(); + + // Assert + EXPECT_EQ(implementations.size(), 0); +} + +TEST(ServiceProvider, provideImplementation) +{ + // Arrange + Base::ServiceProvider serviceProvider; + + serviceProvider.implement(new FirstServiceImplementation); + + // Act + auto implementation = serviceProvider.provide(); + + // Assert + EXPECT_NE(implementation, nullptr); + EXPECT_EQ(implementation->foo(), "first"); +} + +TEST(ServiceProvider, provideLatestImplementation) +{ + // Arrange + Base::ServiceProvider serviceProvider; + + serviceProvider.implement(new FirstServiceImplementation); + serviceProvider.implement(new SecondServiceImplementation); + + // Act + auto implementation = serviceProvider.provide(); + + // Assert + EXPECT_NE(implementation, nullptr); + EXPECT_EQ(implementation->foo(), "second"); +} + +TEST(ServiceProvider, provideAllImplementations) +{ + // Arrange + Base::ServiceProvider serviceProvider; + + serviceProvider.implement(new FirstServiceImplementation); + serviceProvider.implement(new SecondServiceImplementation); + + // Act + auto implementations = serviceProvider.all(); + auto it = implementations.begin(); + + // Assert + // Implementations should be available in order from the most recent one + EXPECT_EQ((*it)->foo(), "second"); + ++it; + EXPECT_EQ((*it)->foo(), "first"); + ++it; + EXPECT_EQ(it, implementations.end()); +}