mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-26 23:55:40 +00:00
319 lines
8.6 KiB
C++
319 lines
8.6 KiB
C++
#ifndef INCLUDED_UTIL_CONFIG_H
|
|
#define INCLUDED_UTIL_CONFIG_H
|
|
|
|
#include "Util/GenericValue.h"
|
|
#include <map>
|
|
#include <memory>
|
|
#include <iterator>
|
|
#include <exception>
|
|
|
|
namespace Util
|
|
{
|
|
namespace Config
|
|
{
|
|
class Node
|
|
{
|
|
private:
|
|
typedef std::shared_ptr<Node> ptr_t;
|
|
typedef std::shared_ptr<const Node> const_ptr_t;
|
|
const std::string m_key; // this cannot be changed (maps depend on it)
|
|
std::shared_ptr<GenericValue> m_value; // null pointer marks this node as an invalid, empty node
|
|
ptr_t m_next_sibling;
|
|
ptr_t m_first_child;
|
|
ptr_t m_last_child;
|
|
std::map<std::string, ptr_t> m_children;
|
|
mutable std::map<std::string, Node> m_missing_nodes; // missing nodes from failed queries (must also be empty)
|
|
bool m_missing = false;
|
|
|
|
void Destroy()
|
|
{
|
|
m_value.reset();
|
|
m_next_sibling.reset();
|
|
m_first_child.reset();
|
|
m_last_child.reset();
|
|
m_children.clear();
|
|
}
|
|
|
|
void CheckEmptyOrMissing() const;
|
|
const Node &MissingNode(const std::string &key) const;
|
|
void AddChild(Node &parent, ptr_t &node);
|
|
void DeepCopy(const Node &that);
|
|
void Swap(Node &rhs);
|
|
Node(); // prohibit accidental/unintentional creation of empty nodes
|
|
|
|
public:
|
|
class const_iterator;
|
|
|
|
class iterator
|
|
{
|
|
private:
|
|
ptr_t m_node;
|
|
friend class const_iterator;
|
|
public:
|
|
inline iterator(ptr_t node = ptr_t())
|
|
: m_node(node)
|
|
{}
|
|
inline iterator(const iterator &it)
|
|
: m_node(it.m_node)
|
|
{}
|
|
inline iterator operator++()
|
|
{
|
|
// Prefix increment
|
|
m_node = m_node->m_next_sibling;
|
|
return *this;
|
|
}
|
|
inline iterator operator++(int)
|
|
{
|
|
// Postfix increment
|
|
iterator current(*this);
|
|
m_node = m_node->m_next_sibling;
|
|
return *this;
|
|
}
|
|
inline Node &operator*() const
|
|
{
|
|
return *m_node;
|
|
}
|
|
inline ptr_t operator->() const
|
|
{
|
|
return m_node;
|
|
}
|
|
inline bool operator==(const iterator &rhs) const
|
|
{
|
|
return m_node == rhs.m_node;
|
|
}
|
|
inline bool operator!=(const iterator &rhs) const
|
|
{
|
|
return m_node != rhs.m_node;
|
|
}
|
|
};
|
|
|
|
class const_iterator
|
|
{
|
|
private:
|
|
const_ptr_t m_node;
|
|
public:
|
|
inline const_iterator()
|
|
: m_node(const_ptr_t())
|
|
{}
|
|
inline const_iterator(const_ptr_t node)
|
|
: m_node(node)
|
|
{}
|
|
inline const_iterator(ptr_t node)
|
|
: m_node(std::const_pointer_cast<const Node>(node))
|
|
{}
|
|
inline const_iterator(const const_iterator &it)
|
|
: m_node(it.m_node)
|
|
{}
|
|
inline const_iterator(const Node::iterator &it)
|
|
: m_node(it.m_node)
|
|
{}
|
|
inline const_iterator operator++()
|
|
{
|
|
// Prefix increment
|
|
m_node = m_node->m_next_sibling;
|
|
return *this;
|
|
}
|
|
inline const_iterator operator++(int)
|
|
{
|
|
// Postfix increment
|
|
//iterator current(*this); //unreferenced local variable
|
|
m_node = m_node->m_next_sibling;
|
|
return *this;
|
|
}
|
|
inline const Node &operator*() const
|
|
{
|
|
return *m_node;
|
|
}
|
|
inline const_ptr_t operator->() const
|
|
{
|
|
return m_node;
|
|
}
|
|
inline bool operator==(const const_iterator &rhs) const
|
|
{
|
|
return m_node == rhs.m_node;
|
|
}
|
|
inline bool operator!=(const const_iterator &rhs) const
|
|
{
|
|
return m_node != rhs.m_node;
|
|
}
|
|
};
|
|
|
|
inline iterator begin()
|
|
{
|
|
return iterator(m_first_child);
|
|
}
|
|
|
|
inline iterator end()
|
|
{
|
|
return iterator();
|
|
}
|
|
|
|
inline const_iterator begin() const
|
|
{
|
|
return iterator(m_first_child);
|
|
}
|
|
|
|
inline const_iterator end() const
|
|
{
|
|
return iterator();
|
|
}
|
|
|
|
const inline std::string &Key() const
|
|
{
|
|
return m_key;
|
|
}
|
|
|
|
inline std::shared_ptr<GenericValue> GetValue() const
|
|
{
|
|
return m_value;
|
|
}
|
|
|
|
inline void SetValue(const std::shared_ptr<GenericValue> &value)
|
|
{
|
|
m_value = value;
|
|
}
|
|
|
|
template <typename T>
|
|
const T &Value() const
|
|
{
|
|
CheckEmptyOrMissing();
|
|
return m_value->Value<T>();
|
|
}
|
|
|
|
template <typename T>
|
|
T ValueAs() const
|
|
{
|
|
CheckEmptyOrMissing();
|
|
return m_value->ValueAs<T>();
|
|
}
|
|
|
|
template <typename T>
|
|
T ValueAsDefault(const T &default_value) const
|
|
{
|
|
if (Empty())
|
|
return default_value;
|
|
return m_value->ValueAs<T>();
|
|
}
|
|
|
|
// Set value of this node
|
|
template <typename T>
|
|
inline void SetValue(const T &value)
|
|
{
|
|
if (!m_missing)
|
|
{
|
|
if (!Empty() && m_value->Is<T>())
|
|
m_value->Set(value);
|
|
else
|
|
m_value = std::make_shared<ValueInstance<T>>(value);
|
|
}
|
|
else
|
|
throw std::range_error(Util::Format() << "Node \"" << m_key << "\" does not exist");
|
|
}
|
|
|
|
// char* is a troublesome case because we want to convert it to std::string
|
|
inline void SetValue(const char *value)
|
|
{
|
|
SetValue<std::string>(value);
|
|
}
|
|
|
|
// Add a child node. Multiple nodes of the same key may be added but only
|
|
// when specified as leaves. For example, adding "foo/bar" twice will
|
|
// result in one "foo" with two "bar" children.
|
|
template <typename T>
|
|
Node &Add(const std::string &path, const T &value)
|
|
{
|
|
std::vector<std::string> keys = Util::Format(path).Split('/');
|
|
Node *parent = this;
|
|
ptr_t node;
|
|
for (size_t i = 0; i < keys.size(); i++)
|
|
{
|
|
bool leaf = i == keys.size() - 1;
|
|
auto it = parent->m_children.find(keys[i]);
|
|
if (leaf || it == parent->m_children.end())
|
|
{
|
|
// Create node at this level
|
|
node = std::make_shared<Node>(keys[i]);
|
|
// The leaf node gets the value
|
|
if (leaf)
|
|
node->SetValue(value);
|
|
// Attach node to parent and move down to next nesting level: last
|
|
// created node is new parent
|
|
AddChild(*parent, node);
|
|
parent = node.get();
|
|
}
|
|
else
|
|
{
|
|
// Descend deeper...
|
|
parent = it->second.get();
|
|
}
|
|
}
|
|
return *node;
|
|
}
|
|
|
|
Node &Add(const std::string &path)
|
|
{
|
|
return Add(path, std::string());
|
|
}
|
|
|
|
// Set the value of the matching child node if it exists, else add new
|
|
template <typename T>
|
|
void Set(const std::string &key, const T &value)
|
|
{
|
|
Node *node = TryGet(key);
|
|
if (node)
|
|
node->SetValue(value);
|
|
else
|
|
Add(key, value);
|
|
}
|
|
|
|
// True if value is empty (does not exist)
|
|
inline bool Empty() const
|
|
{
|
|
return !m_value;
|
|
}
|
|
|
|
// True if value exists (is not empty)
|
|
inline bool Exists() const
|
|
{
|
|
return !Empty();
|
|
}
|
|
|
|
// True if no keys under this node
|
|
inline bool IsLeaf() const
|
|
{
|
|
return m_children.empty();
|
|
}
|
|
|
|
// True if this node has keys
|
|
inline bool HasChildren() const
|
|
{
|
|
return !IsLeaf();
|
|
}
|
|
|
|
// Always succeeds -- failed lookups permanently create an empty node.
|
|
// Use with caution. Intended for hard-coded lookups.
|
|
const Node &operator[](const std::string &path) const;
|
|
|
|
// These throw if the node is missing
|
|
Node &Get(const std::string &path);
|
|
const Node &Get(const std::string &path) const;
|
|
|
|
// This returns nullptr if node is missing
|
|
Node *TryGet(const std::string &path);
|
|
const Node *TryGet(const std::string &path) const;
|
|
|
|
void Serialize(std::ostream *os, size_t indent_level = 0) const;
|
|
std::string ToString(size_t indent_level = 0) const;
|
|
Node &operator=(const Node &rhs);
|
|
Node& operator=(Node&& rhs) noexcept;
|
|
Node(const std::string &key);
|
|
Node(const std::string &key, const std::string &value);
|
|
Node(const Node &that);
|
|
Node(Node&& that) noexcept;
|
|
~Node();
|
|
};
|
|
} // Config
|
|
} // Util
|
|
|
|
#endif // INCLUDED_UTIL_CONFIG_H
|