1 /* 2 * \brief Runtime environment to create (mockable) objects. 3 * 4 * \copyright Copyright (c) 2017-2021 Governikus GmbH & Co. KG, Germany 5 */ 6 7 #pragma once 8 9 #include <functional> 10 #include <type_traits> 11 12 #include <QCoreApplication> 13 #include <QDebug> 14 #include <QMap> 15 #include <QMetaObject> 16 #include <QMetaType> 17 #include <QObject> 18 #include <QObjectCleanupHandler> 19 #include <QPointer> 20 #include <QReadLocker> 21 #include <QReadWriteLock> 22 #include <QSharedPointer> 23 #include <QThread> 24 #include <QWeakPointer> 25 #include <QWriteLocker> 26 27 #ifndef QT_NO_DEBUG 28 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 29 #include <QMutableVectorIterator> 30 #endif 31 32 #include <QVector> 33 #endif 34 35 class test_Env; 36 37 namespace governikus 38 { 39 40 template<typename T> T* singleton(); 41 template<typename T, typename ... Args> T createNewObject(Args&& ... pArgs); 42 43 class Env 44 { 45 public: 46 struct ThreadSafe {}; 47 48 private: 49 friend class ::test_Env; 50 Q_DISABLE_COPY(Env) 51 using Identifier = const char*; 52 53 #ifndef QT_NO_DEBUG 54 class FuncWrapperBase 55 { 56 protected: 57 int mCounter = 0; 58 59 public: getCounter()60 [[nodiscard]] inline int getCounter() const 61 { 62 return mCounter; 63 } 64 65 reset()66 inline void reset() 67 { 68 mCounter = 0; 69 } 70 71 72 virtual ~FuncWrapperBase(); 73 }; 74 75 template<typename T, typename ... Args> 76 class FuncWrapper final 77 : public FuncWrapperBase 78 { 79 private: 80 const std::function<T(Args ...)> mFunc; 81 82 public: FuncWrapper(std::function<T (Args...)> pFunc)83 explicit FuncWrapper(std::function<T(Args ...)> pFunc) 84 : mFunc(std::move(pFunc)) 85 { 86 } 87 88 operator()89 T operator()(Args&& ... pArgs) 90 { 91 ++mCounter; 92 return mFunc(pArgs ...); 93 } 94 95 96 }; 97 98 using Wrapper = QSharedPointer<FuncWrapperBase>; 99 QVector<Wrapper> mInstancesCreator; 100 QMap<Identifier, void*> mInstancesSingleton; 101 mutable QReadWriteLock mLock; 102 #endif 103 104 QPointer<QObjectCleanupHandler> mSingletonHandler; 105 QVector<std::function<void* (bool)>> mSingletonCreator; 106 107 QMap<Identifier, QWeakPointer<QObject>> mSharedInstances; 108 mutable QReadWriteLock mSharedInstancesLock; 109 110 static Env& getInstance(); 111 112 template<typename T> createSingleton()113 T* createSingleton() 114 { 115 Q_ASSERT(!mSingletonHandler.isNull()); 116 #ifndef QT_NO_DEBUG 117 if (!QCoreApplication::startingUp() && !QCoreApplication::applicationName().startsWith(QLatin1String("Test_"))) 118 { 119 Q_ASSERT(QThread::currentThread()->objectName() == QLatin1String("MainThread")); 120 } 121 #endif 122 123 qDebug() << "Create singleton:" << T::staticMetaObject.className(); 124 125 T* ptr = nullptr; 126 if constexpr (std::is_abstract<T>::value && std::is_destructible<T>::value) 127 { 128 ptr = createNewObject<T*>(); 129 } 130 else 131 { 132 ptr = new T(); 133 } 134 135 QObject::connect(ptr, &QObject::destroyed, ptr, [] { 136 qDebug() << "Destroy singleton:" << T::staticMetaObject.className(); 137 }); 138 mSingletonHandler->add(ptr); 139 mSingletonCreator << std::bind(&Env::getOrCreateSingleton<T>, this, std::placeholders::_1); 140 141 return ptr; 142 } 143 144 145 template<typename T> 146 T* getOrCreateSingleton(bool pCreate = false) 147 { 148 static QPointer<T> instance = createSingleton<T>(); 149 150 if (Q_UNLIKELY(pCreate)) 151 { 152 // It's not thread-safe! So Env::init() should be the only one! 153 Q_ASSERT(instance.isNull()); 154 instance = createSingleton<T>(); 155 } 156 157 return instance; 158 } 159 160 161 template<typename T> fetchRealSingleton()162 inline T* fetchRealSingleton() 163 { 164 if constexpr (QtPrivate::IsPointerToTypeDerivedFromQObject<T*>::Value) 165 { 166 return getOrCreateSingleton<T>(); 167 } 168 else 169 { 170 if constexpr (std::is_abstract<T>::value && std::is_destructible<T>::value) 171 { 172 static_assert(std::has_virtual_destructor<T>::value, "Destructor must be virtual"); 173 return singleton<T>(); 174 } 175 else 176 { 177 return &T::getInstance(); 178 } 179 } 180 } 181 182 183 template<typename T> checkObjectInfo(Identifier pId,T * pObject)184 inline typename std::enable_if<QtPrivate::IsGadgetHelper<T>::IsRealGadget, T*>::type checkObjectInfo(Identifier pId, T* pObject) const 185 { 186 Q_UNUSED(pId) 187 return pObject; 188 } 189 190 191 template<typename T> checkObjectInfo(Identifier pId,T * pObject)192 inline typename std::enable_if<QtPrivate::IsPointerToTypeDerivedFromQObject<T*>::Value, T*>::type checkObjectInfo(Identifier pId, T* pObject) const 193 { 194 if (!std::is_base_of<ThreadSafe, T>() && pObject->thread() != QThread::currentThread()) 195 { 196 qWarning() << pId << "was created in" << pObject->thread()->objectName() << "but is requested by" << QThread::currentThread()->objectName(); 197 #ifndef QT_NO_DEBUG 198 Q_ASSERT(QCoreApplication::applicationName().startsWith(QLatin1String("Test_global_Env"))); 199 #endif 200 } 201 202 return pObject; 203 } 204 205 206 template<typename T> fetchSingleton()207 inline T* fetchSingleton() 208 { 209 static_assert(QtPrivate::IsGadgetHelper<T>::IsRealGadget || QtPrivate::IsPointerToTypeDerivedFromQObject<T*>::Value, 210 "Singletons needs to be a Q_GADGET or an QObject/Q_OBJECT"); 211 212 const Identifier id = T::staticMetaObject.className(); 213 void* obj = nullptr; 214 #ifndef QT_NO_DEBUG 215 const QReadLocker locker(&mLock); 216 obj = mInstancesSingleton.value(id); 217 if (!obj) 218 #endif 219 obj = fetchRealSingleton<T>(); 220 Q_ASSERT(obj); 221 return checkObjectInfo(id, static_cast<T*>(obj)); 222 } 223 224 225 template<typename T, typename ... Args> newObject(Args &&...pArgs)226 inline T newObject(Args&& ... pArgs) const 227 { 228 if constexpr (std::is_constructible<typename std::remove_pointer<T>::type, Args ...>::value) 229 { 230 if constexpr (std::is_pointer<T>::value) 231 { 232 using t = typename std::remove_pointer<T>::type; 233 return new t(std::forward<Args>(pArgs) ...); 234 } 235 else 236 { 237 return T(std::forward<Args>(pArgs) ...); 238 } 239 } 240 else 241 { 242 static_assert(std::is_pointer<T>::value, "It is impossible to return implementation of interface by value. Use pointer or add constructor!"); 243 auto obj = createNewObject<T>(std::forward<Args>(pArgs) ...); 244 Q_ASSERT(obj); 245 return obj; 246 } 247 } 248 249 250 template<typename T, typename ... Args> createObject(Args &&...pArgs)251 T createObject(Args&& ... pArgs) const 252 { 253 #ifndef QT_NO_DEBUG 254 { 255 QReadLocker locker(&mLock); 256 257 // copy QSharedPointer "mock" to increase ref-counter. Otherwise 258 // unlock would allow to delete the wrapper. 259 for (auto mock : qAsConst(mInstancesCreator)) // clazy:exclude=range-loop 260 { 261 auto creator = mock.dynamicCast<FuncWrapper<T, Args ...>>(); 262 if (creator) 263 { 264 locker.unlock(); 265 return (*creator)(std::forward<Args>(pArgs) ...); 266 } 267 } 268 } 269 #endif 270 271 return newObject<T>(std::forward<Args>(pArgs) ...); 272 } 273 274 initialize()275 void initialize() 276 { 277 Q_ASSERT(mSingletonHandler.isNull()); 278 mSingletonHandler = new QObjectCleanupHandler(); 279 QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, mSingletonHandler.data(), &QObject::deleteLater); 280 281 const auto copy = mSingletonCreator; 282 mSingletonCreator.clear(); 283 for (const auto& func : copy) 284 { 285 func(true); 286 } 287 } 288 289 protected: 290 Env(); 291 ~Env() = default; 292 293 public: init()294 static void init() 295 { 296 getInstance().initialize(); 297 } 298 299 300 template<typename T> getSingleton()301 static T* getSingleton() 302 { 303 return getInstance().fetchSingleton<T>(); 304 } 305 306 307 template<typename T, typename ... Args> create(Args &&...pArgs)308 static T create(Args&& ... pArgs) 309 { 310 return getInstance().createObject<T>(std::forward<Args>(pArgs) ...); 311 } 312 313 314 template<typename T> getShared()315 static QSharedPointer<T> getShared() 316 { 317 static_assert(QtPrivate::IsGadgetHelper<T>::IsRealGadget || QtPrivate::IsPointerToTypeDerivedFromQObject<T*>::Value, 318 "Shared class needs to be a Q_GADGET or an QObject/Q_OBJECT"); 319 320 const Identifier className = T::staticMetaObject.className(); 321 322 auto& holder = getInstance(); 323 holder.mSharedInstancesLock.lockForRead(); 324 QSharedPointer<T> shared = qSharedPointerCast<T>(holder.mSharedInstances.value(className)); 325 holder.mSharedInstancesLock.unlock(); 326 327 if (!shared) 328 { 329 const QWriteLocker locker(&holder.mSharedInstancesLock); 330 shared = qSharedPointerCast<T>(holder.mSharedInstances.value(className)); 331 if (!shared) 332 { 333 qDebug() << "Spawn shared instance:" << className; 334 shared = QSharedPointer<T>::create(); 335 holder.mSharedInstances.insert(className, shared.toWeakRef()); 336 } 337 } 338 339 return shared; 340 } 341 342 343 #ifndef QT_NO_DEBUG 344 static void resetCounter(); 345 static void clear(); 346 static void set(const QMetaObject& pMetaObject, void* pObject = nullptr); 347 348 template<typename T, typename ... Args> getCounter()349 static int getCounter() 350 { 351 auto& holder = getInstance(); 352 const QReadLocker locker(&holder.mLock); 353 354 for (const auto& mock : qAsConst(holder.mInstancesCreator)) 355 { 356 if (mock.dynamicCast<FuncWrapper<T, Args ...>>()) 357 { 358 return mock->getCounter(); 359 } 360 } 361 362 return -1; // There is no mock... use setCreator! 363 } 364 365 366 template<typename T, typename ... Args> setCreator(std::function<T (Args...)> pFunc)367 static void setCreator(std::function<T(Args ...)> pFunc) 368 { 369 Q_ASSERT(pFunc); 370 371 const auto& value = QSharedPointer<FuncWrapper<T, Args ...>>::create(std::move(pFunc)); 372 373 auto& holder = getInstance(); 374 const QWriteLocker locker(&holder.mLock); 375 376 QMutableVectorIterator<Wrapper> iter(holder.mInstancesCreator); 377 while (iter.hasNext()) 378 { 379 iter.next(); 380 if (iter.value().dynamicCast<FuncWrapper<T, Args ...>>()) 381 { 382 iter.setValue(value); 383 return; 384 } 385 } 386 387 holder.mInstancesCreator << value; 388 } 389 390 391 static void setShared(const QMetaObject& pMetaObject, const QSharedPointer<QObject>& pObject); 392 #endif 393 394 }; 395 396 } // namespace governikus 397