You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

490 lines
13 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 3
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, you may find one here:
  18. * https://www.gnu.org/licenses/gpl-3.0.en.html
  19. * or you may search the http://www.gnu.org website for the version 32 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #ifndef PROPERTY_HOLDER_H
  24. #define PROPERTY_HOLDER_H
  25. #include <any>
  26. #include <string>
  27. #include <unordered_map>
  28. #include <type_traits>
  29. #include <optional>
  30. #include <concepts>
  31. #include <cstdint>
  32. /**
  33. * @brief A C++20 property system for arbitrary key-value storage with type safety.
  34. *
  35. * This class provides a flexible way to store arbitrary typed properties using
  36. * string keys. It supports type-safe getters/setters with automatic type checking
  37. * and optional default values.
  38. *
  39. * Example usage:
  40. * @code
  41. * PROPERTY_HOLDER props;
  42. *
  43. * // Setting properties
  44. * props.SetProperty("persist", true);
  45. * props.SetProperty("max_width", 500);
  46. * props.SetProperty("label", std::string("My Label"));
  47. *
  48. * // Getting properties with type checking
  49. * if (auto persist = props.GetProperty<bool>("persist")) {
  50. * std::cout << "Persist: " << *persist << std::endl;
  51. * }
  52. *
  53. * // Getting with default value
  54. * bool shouldPersist = props.GetPropertyOr("persist", false);
  55. * int width = props.GetPropertyOr("max_width", 100);
  56. *
  57. * // Checking existence
  58. * if (props.HasProperty("label")) {
  59. * // Property exists
  60. * }
  61. *
  62. * // Removing properties
  63. * props.RemoveProperty("label");
  64. * props.Clear();
  65. * @endcode
  66. */
  67. /**
  68. * @brief Concept for types that can be used as property values
  69. */
  70. template<typename T>
  71. concept PropertyValueType = std::copy_constructible<T> && std::destructible<T>;
  72. class PROPERTY_HOLDER
  73. {
  74. public:
  75. /// Magic value for memory validation (ASCII: "PROP" + "HLDR")
  76. static constexpr uint64_t MAGIC_VALUE = 0x50524F5048444C52ULL;
  77. /**
  78. * @brief Default constructor - initializes magic value
  79. */
  80. PROPERTY_HOLDER() :
  81. m_magic( MAGIC_VALUE )
  82. {
  83. }
  84. /**
  85. * @brief Copy constructor - maintains magic value
  86. */
  87. PROPERTY_HOLDER( const PROPERTY_HOLDER& other ) :
  88. m_magic( MAGIC_VALUE ),
  89. m_properties( other.m_properties )
  90. {
  91. }
  92. /**
  93. * @brief Move constructor - maintains magic value
  94. */
  95. PROPERTY_HOLDER( PROPERTY_HOLDER&& other ) noexcept :
  96. m_magic( MAGIC_VALUE ),
  97. m_properties( std::move( other.m_properties ) )
  98. {
  99. }
  100. /**
  101. * @brief Copy assignment operator
  102. */
  103. PROPERTY_HOLDER& operator=( const PROPERTY_HOLDER& other )
  104. {
  105. if( this != &other )
  106. m_properties = other.m_properties;
  107. return *this;
  108. }
  109. /**
  110. * @brief Move assignment operator
  111. */
  112. PROPERTY_HOLDER& operator=( PROPERTY_HOLDER&& other ) noexcept
  113. {
  114. if( this != &other )
  115. m_properties = std::move( other.m_properties );
  116. return *this;
  117. }
  118. /**
  119. * @brief Destructor - clears magic value to detect use-after-free
  120. */
  121. ~PROPERTY_HOLDER()
  122. {
  123. m_magic = 0xDEADBEEFDEADBEEFULL; // Clear magic to detect use-after-free
  124. }
  125. /**
  126. * @brief Check if this instance has a valid magic value
  127. * @return true if magic value is valid, false otherwise
  128. */
  129. bool IsValid() const noexcept { return m_magic == MAGIC_VALUE; }
  130. /**
  131. * @brief Safely cast a void pointer to PROPERTY_HOLDER*
  132. * @param ptr Pointer to validate and cast
  133. * @return PROPERTY_HOLDER* if valid, nullptr otherwise
  134. */
  135. static PROPERTY_HOLDER* SafeCast( void* aPtr ) noexcept
  136. {
  137. if( !aPtr )
  138. return nullptr;
  139. try
  140. {
  141. PROPERTY_HOLDER* aCandidate = reinterpret_cast<PROPERTY_HOLDER*>( aPtr );
  142. if( aCandidate->m_magic == MAGIC_VALUE )
  143. {
  144. return aCandidate;
  145. }
  146. }
  147. catch( ... )
  148. {
  149. // Any exception means invalid memory
  150. }
  151. return nullptr;
  152. }
  153. /**
  154. * @brief Safely cast a const void pointer to const PROPERTY_HOLDER*
  155. * @param ptr Pointer to validate and cast
  156. * @return const PROPERTY_HOLDER* if valid, nullptr otherwise
  157. */
  158. static const PROPERTY_HOLDER* SafeCast( const void* aPtr ) noexcept
  159. {
  160. if( !aPtr )
  161. return nullptr;
  162. try
  163. {
  164. const PROPERTY_HOLDER* aCandidate = reinterpret_cast<const PROPERTY_HOLDER*>( aPtr );
  165. if( aCandidate->m_magic == MAGIC_VALUE )
  166. {
  167. return aCandidate;
  168. }
  169. }
  170. catch( ... )
  171. {
  172. // Any exception means invalid memory
  173. }
  174. return nullptr;
  175. }
  176. /**
  177. * @brief Safely delete a PROPERTY_HOLDER from client data
  178. * @param ptr Pointer from client data to validate and delete
  179. * @return true if successfully deleted, false if invalid pointer
  180. */
  181. static bool SafeDelete( void* aPtr ) noexcept
  182. {
  183. PROPERTY_HOLDER* aHolder = SafeCast( aPtr );
  184. if( aHolder )
  185. {
  186. delete aHolder;
  187. return true;
  188. }
  189. return false;
  190. }
  191. static bool SafeDelete( PROPERTY_HOLDER* aHolder ) noexcept
  192. {
  193. if( aHolder )
  194. {
  195. delete aHolder;
  196. return true;
  197. }
  198. return false;
  199. }
  200. /**
  201. * @brief Set a property with the given key and value.
  202. * @tparam T The type of the value to store
  203. * @param key The property key
  204. * @param value The value to store
  205. * @return true if property was set, false if object is invalid
  206. */
  207. template <typename T>
  208. bool SetProperty( const std::string& aKey, T&& aValue )
  209. {
  210. if( !IsValid() )
  211. return false;
  212. m_properties[aKey] = std::forward<T>( aValue );
  213. return true;
  214. }
  215. /**
  216. * @brief Get a property value with type checking.
  217. * @tparam T The expected type of the property
  218. * @param key The property key
  219. * @return std::optional<T> containing the value if found and type matches, nullopt otherwise
  220. */
  221. template <typename T>
  222. std::optional<T> GetProperty( const std::string& aKey ) const
  223. {
  224. if( !IsValid() )
  225. return std::nullopt;
  226. auto it = m_properties.find( aKey );
  227. if( it == m_properties.end() )
  228. {
  229. return std::nullopt;
  230. }
  231. try
  232. {
  233. return std::any_cast<T>( it->second );
  234. }
  235. catch( const std::bad_any_cast& )
  236. {
  237. return std::nullopt;
  238. }
  239. }
  240. /**
  241. * @brief Get a property value with a default fallback.
  242. * @tparam T The expected type of the property
  243. * @param key The property key
  244. * @param defaultValue The value to return if property doesn't exist or type mismatch
  245. * @return The property value or the default value
  246. */
  247. template<typename T>
  248. T GetPropertyOr(const std::string& aKey, T&& aDefaultValue) const
  249. {
  250. if( auto aValue = GetProperty<T>( aKey ) )
  251. return *aValue;
  252. return std::forward<T>(aDefaultValue);
  253. }
  254. /**
  255. * @brief Check if a property exists.
  256. * @param key The property key
  257. * @return true if the property exists and object is valid, false otherwise
  258. */
  259. bool HasProperty( const std::string& aKey ) const
  260. {
  261. if( !IsValid() )
  262. return false;
  263. return m_properties.find( aKey ) != m_properties.end();
  264. }
  265. /**
  266. * @brief Remove a property.
  267. * @param key The property key
  268. * @return true if the property was removed, false if it didn't exist or object is invalid
  269. */
  270. bool RemoveProperty( const std::string& aKey )
  271. {
  272. if( !IsValid() )
  273. return false;
  274. return m_properties.erase( aKey ) > 0;
  275. }
  276. /**
  277. * @brief Clear all properties.
  278. * @return true if cleared successfully, false if object is invalid
  279. */
  280. bool Clear()
  281. {
  282. if( !IsValid() )
  283. return false;
  284. m_properties.clear();
  285. return true;
  286. }
  287. /**
  288. * @brief Get the number of stored properties.
  289. * @return The number of properties, or 0 if object is invalid
  290. */
  291. size_t Size() const
  292. {
  293. if( !IsValid() )
  294. return 0;
  295. return m_properties.size();
  296. }
  297. /**
  298. * @brief Check if there are no properties stored.
  299. * @return true if empty or object is invalid, false otherwise
  300. */
  301. bool Empty() const
  302. {
  303. if( !IsValid() )
  304. return true;
  305. return m_properties.empty();
  306. }
  307. /**
  308. * @brief Get all property keys.
  309. * @return A vector of all property keys (empty if object is invalid)
  310. */
  311. std::vector<std::string> GetKeys() const
  312. {
  313. if( !IsValid() )
  314. return {};
  315. std::vector<std::string> keys;
  316. keys.reserve( m_properties.size() );
  317. for( const auto& [key, value] : m_properties )
  318. keys.push_back( key );
  319. return keys;
  320. }
  321. /**
  322. * @brief Get the type information for a property.
  323. * @param key The property key
  324. * @return std::optional<std::type_info> containing type info if property exists and object is valid
  325. */
  326. std::optional<std::reference_wrapper<const std::type_info>> GetPropertyType( const std::string& aKey ) const
  327. {
  328. if( !IsValid() )
  329. return std::nullopt;
  330. auto it = m_properties.find( aKey );
  331. if( it == m_properties.end() )
  332. return std::nullopt;
  333. return std::cref( it->second.type() );
  334. }
  335. /**
  336. * @brief Check if a property exists and has the expected type.
  337. * @tparam T The expected type
  338. * @param key The property key
  339. * @return true if property exists, has type T, and object is valid
  340. */
  341. template <typename T>
  342. bool HasPropertyOfType( const std::string& aKey ) const
  343. {
  344. if( !IsValid() )
  345. return false;
  346. auto it = m_properties.find( aKey );
  347. if( it == m_properties.end() )
  348. return false;
  349. return it->second.type() == typeid( T );
  350. }
  351. /**
  352. * @brief Type-safe property setter that only accepts valid property types
  353. */
  354. template<PropertyValueType T>
  355. bool SetTypedProperty(const std::string& aKey, T&& aValue)
  356. {
  357. return SetProperty(aKey, std::forward<T>(aValue));
  358. }
  359. private:
  360. uint64_t m_magic; ///< Magic value for memory validation
  361. /**
  362. * @brief Internal storage for properties using string keys and any values.
  363. *
  364. * This uses std::any to allow storing any type of value, with type safety
  365. * provided by the GetProperty and SetProperty methods.
  366. */
  367. std::unordered_map<std::string, std::any> m_properties;
  368. };
  369. /**
  370. * @brief Mixin class to add property support to any class.
  371. *
  372. * Example usage:
  373. * @code
  374. * class MyWidget : public SomeBaseClass, public PROPERTY_MIXIN
  375. * {
  376. * public:
  377. * MyWidget() {
  378. * SetProperty("default_width", 200);
  379. * }
  380. * };
  381. *
  382. * MyWidget widget;
  383. * widget.SetProperty("persist", false);
  384. * bool persist = widget.GetPropertyOr("persist", true);
  385. * @endcode
  386. */
  387. class PROPERTY_MIXIN
  388. {
  389. public:
  390. /**
  391. * @brief Get the property holder for this object.
  392. * @return Reference to the property holder
  393. */
  394. PROPERTY_HOLDER& GetPropertyHolder() { return m_propertyHolder; }
  395. const PROPERTY_HOLDER& GetPropertyHolder() const { return m_propertyHolder; }
  396. // Convenience methods that delegate to the property holder
  397. template<typename T>
  398. void SetProperty(const std::string& aKey, T&& aValue)
  399. {
  400. m_propertyHolder.SetProperty(aKey, std::forward<T>(aValue));
  401. }
  402. template<typename T>
  403. std::optional<T> GetProperty(const std::string& aKey) const
  404. {
  405. return m_propertyHolder.GetProperty<T>(aKey);
  406. }
  407. template<typename T>
  408. T GetPropertyOr(const std::string& aKey, T&& aDefaultValue) const
  409. {
  410. return m_propertyHolder.GetPropertyOr(aKey, std::forward<T>(aDefaultValue));
  411. }
  412. bool HasProperty(const std::string& aKey) const
  413. {
  414. return m_propertyHolder.HasProperty(aKey);
  415. }
  416. bool RemoveProperty(const std::string& aKey)
  417. {
  418. return m_propertyHolder.RemoveProperty(aKey);
  419. }
  420. template<typename T>
  421. bool HasPropertyOfType(const std::string& aKey) const
  422. {
  423. return m_propertyHolder.HasPropertyOfType<T>(aKey);
  424. }
  425. private:
  426. PROPERTY_HOLDER m_propertyHolder;
  427. };
  428. #endif // PROPERTY_HOLDER_H