mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-13 17:53:11 +02:00
491 lines
13 KiB
C++
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
|