kicad-source/include/property_holder.h
2025-08-07 15:29:43 +02:00

491 lines
13 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* https://www.gnu.org/licenses/gpl-3.0.en.html
* or you may search the http://www.gnu.org website for the version 32 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifndef PROPERTY_HOLDER_H
#define PROPERTY_HOLDER_H
#include <any>
#include <string>
#include <unordered_map>
#include <type_traits>
#include <optional>
#include <concepts>
#include <cstdint>
/**
* @brief A C++20 property system for arbitrary key-value storage with type safety.
*
* This class provides a flexible way to store arbitrary typed properties using
* string keys. It supports type-safe getters/setters with automatic type checking
* and optional default values.
*
* Example usage:
* @code
* PROPERTY_HOLDER props;
*
* // Setting properties
* props.SetProperty("persist", true);
* props.SetProperty("max_width", 500);
* props.SetProperty("label", std::string("My Label"));
*
* // Getting properties with type checking
* if (auto persist = props.GetProperty<bool>("persist")) {
* std::cout << "Persist: " << *persist << std::endl;
* }
*
* // Getting with default value
* bool shouldPersist = props.GetPropertyOr("persist", false);
* int width = props.GetPropertyOr("max_width", 100);
*
* // Checking existence
* if (props.HasProperty("label")) {
* // Property exists
* }
*
* // Removing properties
* props.RemoveProperty("label");
* props.Clear();
* @endcode
*/
/**
* @brief Concept for types that can be used as property values
*/
template<typename T>
concept PropertyValueType = std::copy_constructible<T> && std::destructible<T>;
class PROPERTY_HOLDER
{
public:
/// Magic value for memory validation (ASCII: "PROP" + "HLDR")
static constexpr uint64_t MAGIC_VALUE = 0x50524F5048444C52ULL;
/**
* @brief Default constructor - initializes magic value
*/
PROPERTY_HOLDER() :
m_magic( MAGIC_VALUE )
{
}
/**
* @brief Copy constructor - maintains magic value
*/
PROPERTY_HOLDER( const PROPERTY_HOLDER& other ) :
m_magic( MAGIC_VALUE ),
m_properties( other.m_properties )
{
}
/**
* @brief Move constructor - maintains magic value
*/
PROPERTY_HOLDER( PROPERTY_HOLDER&& other ) noexcept :
m_magic( MAGIC_VALUE ),
m_properties( std::move( other.m_properties ) )
{
}
/**
* @brief Copy assignment operator
*/
PROPERTY_HOLDER& operator=( const PROPERTY_HOLDER& other )
{
if( this != &other )
m_properties = other.m_properties;
return *this;
}
/**
* @brief Move assignment operator
*/
PROPERTY_HOLDER& operator=( PROPERTY_HOLDER&& other ) noexcept
{
if( this != &other )
m_properties = std::move( other.m_properties );
return *this;
}
/**
* @brief Destructor - clears magic value to detect use-after-free
*/
~PROPERTY_HOLDER()
{
m_magic = 0xDEADBEEFDEADBEEFULL; // Clear magic to detect use-after-free
}
/**
* @brief Check if this instance has a valid magic value
* @return true if magic value is valid, false otherwise
*/
bool IsValid() const noexcept { return m_magic == MAGIC_VALUE; }
/**
* @brief Safely cast a void pointer to PROPERTY_HOLDER*
* @param ptr Pointer to validate and cast
* @return PROPERTY_HOLDER* if valid, nullptr otherwise
*/
static PROPERTY_HOLDER* SafeCast( void* aPtr ) noexcept
{
if( !aPtr )
return nullptr;
try
{
PROPERTY_HOLDER* aCandidate = reinterpret_cast<PROPERTY_HOLDER*>( aPtr );
if( aCandidate->m_magic == MAGIC_VALUE )
{
return aCandidate;
}
}
catch( ... )
{
// Any exception means invalid memory
}
return nullptr;
}
/**
* @brief Safely cast a const void pointer to const PROPERTY_HOLDER*
* @param ptr Pointer to validate and cast
* @return const PROPERTY_HOLDER* if valid, nullptr otherwise
*/
static const PROPERTY_HOLDER* SafeCast( const void* aPtr ) noexcept
{
if( !aPtr )
return nullptr;
try
{
const PROPERTY_HOLDER* aCandidate = reinterpret_cast<const PROPERTY_HOLDER*>( aPtr );
if( aCandidate->m_magic == MAGIC_VALUE )
{
return aCandidate;
}
}
catch( ... )
{
// Any exception means invalid memory
}
return nullptr;
}
/**
* @brief Safely delete a PROPERTY_HOLDER from client data
* @param ptr Pointer from client data to validate and delete
* @return true if successfully deleted, false if invalid pointer
*/
static bool SafeDelete( void* aPtr ) noexcept
{
PROPERTY_HOLDER* aHolder = SafeCast( aPtr );
if( aHolder )
{
delete aHolder;
return true;
}
return false;
}
static bool SafeDelete( PROPERTY_HOLDER* aHolder ) noexcept
{
if( aHolder )
{
delete aHolder;
return true;
}
return false;
}
/**
* @brief Set a property with the given key and value.
* @tparam T The type of the value to store
* @param key The property key
* @param value The value to store
* @return true if property was set, false if object is invalid
*/
template <typename T>
bool SetProperty( const std::string& aKey, T&& aValue )
{
if( !IsValid() )
return false;
m_properties[aKey] = std::forward<T>( aValue );
return true;
}
/**
* @brief Get a property value with type checking.
* @tparam T The expected type of the property
* @param key The property key
* @return std::optional<T> containing the value if found and type matches, nullopt otherwise
*/
template <typename T>
std::optional<T> GetProperty( const std::string& aKey ) const
{
if( !IsValid() )
return std::nullopt;
auto it = m_properties.find( aKey );
if( it == m_properties.end() )
{
return std::nullopt;
}
try
{
return std::any_cast<T>( it->second );
}
catch( const std::bad_any_cast& )
{
return std::nullopt;
}
}
/**
* @brief Get a property value with a default fallback.
* @tparam T The expected type of the property
* @param key The property key
* @param defaultValue The value to return if property doesn't exist or type mismatch
* @return The property value or the default value
*/
template<typename T>
T GetPropertyOr(const std::string& aKey, T&& aDefaultValue) const
{
if( auto aValue = GetProperty<T>( aKey ) )
return *aValue;
return std::forward<T>(aDefaultValue);
}
/**
* @brief Check if a property exists.
* @param key The property key
* @return true if the property exists and object is valid, false otherwise
*/
bool HasProperty( const std::string& aKey ) const
{
if( !IsValid() )
return false;
return m_properties.find( aKey ) != m_properties.end();
}
/**
* @brief Remove a property.
* @param key The property key
* @return true if the property was removed, false if it didn't exist or object is invalid
*/
bool RemoveProperty( const std::string& aKey )
{
if( !IsValid() )
return false;
return m_properties.erase( aKey ) > 0;
}
/**
* @brief Clear all properties.
* @return true if cleared successfully, false if object is invalid
*/
bool Clear()
{
if( !IsValid() )
return false;
m_properties.clear();
return true;
}
/**
* @brief Get the number of stored properties.
* @return The number of properties, or 0 if object is invalid
*/
size_t Size() const
{
if( !IsValid() )
return 0;
return m_properties.size();
}
/**
* @brief Check if there are no properties stored.
* @return true if empty or object is invalid, false otherwise
*/
bool Empty() const
{
if( !IsValid() )
return true;
return m_properties.empty();
}
/**
* @brief Get all property keys.
* @return A vector of all property keys (empty if object is invalid)
*/
std::vector<std::string> GetKeys() const
{
if( !IsValid() )
return {};
std::vector<std::string> keys;
keys.reserve( m_properties.size() );
for( const auto& [key, value] : m_properties )
keys.push_back( key );
return keys;
}
/**
* @brief Get the type information for a property.
* @param key The property key
* @return std::optional<std::type_info> containing type info if property exists and object is valid
*/
std::optional<std::reference_wrapper<const std::type_info>> GetPropertyType( const std::string& aKey ) const
{
if( !IsValid() )
return std::nullopt;
auto it = m_properties.find( aKey );
if( it == m_properties.end() )
return std::nullopt;
return std::cref( it->second.type() );
}
/**
* @brief Check if a property exists and has the expected type.
* @tparam T The expected type
* @param key The property key
* @return true if property exists, has type T, and object is valid
*/
template <typename T>
bool HasPropertyOfType( const std::string& aKey ) const
{
if( !IsValid() )
return false;
auto it = m_properties.find( aKey );
if( it == m_properties.end() )
return false;
return it->second.type() == typeid( T );
}
/**
* @brief Type-safe property setter that only accepts valid property types
*/
template<PropertyValueType T>
bool SetTypedProperty(const std::string& aKey, T&& aValue)
{
return SetProperty(aKey, std::forward<T>(aValue));
}
private:
uint64_t m_magic; ///< Magic value for memory validation
/**
* @brief Internal storage for properties using string keys and any values.
*
* This uses std::any to allow storing any type of value, with type safety
* provided by the GetProperty and SetProperty methods.
*/
std::unordered_map<std::string, std::any> m_properties;
};
/**
* @brief Mixin class to add property support to any class.
*
* Example usage:
* @code
* class MyWidget : public SomeBaseClass, public PROPERTY_MIXIN
* {
* public:
* MyWidget() {
* SetProperty("default_width", 200);
* }
* };
*
* MyWidget widget;
* widget.SetProperty("persist", false);
* bool persist = widget.GetPropertyOr("persist", true);
* @endcode
*/
class PROPERTY_MIXIN
{
public:
/**
* @brief Get the property holder for this object.
* @return Reference to the property holder
*/
PROPERTY_HOLDER& GetPropertyHolder() { return m_propertyHolder; }
const PROPERTY_HOLDER& GetPropertyHolder() const { return m_propertyHolder; }
// Convenience methods that delegate to the property holder
template<typename T>
void SetProperty(const std::string& aKey, T&& aValue)
{
m_propertyHolder.SetProperty(aKey, std::forward<T>(aValue));
}
template<typename T>
std::optional<T> GetProperty(const std::string& aKey) const
{
return m_propertyHolder.GetProperty<T>(aKey);
}
template<typename T>
T GetPropertyOr(const std::string& aKey, T&& aDefaultValue) const
{
return m_propertyHolder.GetPropertyOr(aKey, std::forward<T>(aDefaultValue));
}
bool HasProperty(const std::string& aKey) const
{
return m_propertyHolder.HasProperty(aKey);
}
bool RemoveProperty(const std::string& aKey)
{
return m_propertyHolder.RemoveProperty(aKey);
}
template<typename T>
bool HasPropertyOfType(const std::string& aKey) const
{
return m_propertyHolder.HasPropertyOfType<T>(aKey);
}
private:
PROPERTY_HOLDER m_propertyHolder;
};
#endif // PROPERTY_HOLDER_H