Files
libsst/Include/ZUtil/ZKVTree.hpp
2026-04-03 00:22:39 -05:00

822 lines
23 KiB
C++

/*
ZKVTree.hpp
Author: James Russell <jcrussell@762studios.com>
Created: 3/22/2012
Purpose:
ZKVTree defines a KV-Tree that uses strings as both keys and values. The model
supported is hierarchical with no nesting limit, and individual nodes can be addressed
directly or the tree can be iterated. This makes ZKVTree's typical use case as an
in memory representation of hierarchical text formats such as XML and JSON, and can
be used for simpler formats such as INI. When the layout and keys are known
(such as with most INI files), lookup via path string is the easiest route. When the layout
is not known (such as with an XML file with variable numbers of children and attributes),
then iteration is the preferred method of getting access to the data.
Multiple nodes of the same name existing at the same level in the tree is supported.
When a path is given that does not contain the number, then it is assumed to be
referencing the first sibling node at that location (index 0).
Unnamed tree nodes are supported, with the key used to access those being omitted from the
path string but the subscript being required.
If the tree has the following layout, with the values at each node contained
in parenthesis:
Root
|
+-> A (0)
| |
| +-> B (8)
| | |
| | +-> c (16)
| | +-> c (32)
| | +-> (100)
| | +-> (200)
| |
| +-> B (8)
| | |
| | +-> c (64)
| | +-> d (128)
| | +-> (10)
| | +-> (20)
| |
| +-> b (5)
| +-> b (10)
| +-> c (15)
| +-> d (20)
|
+-> B (8)
| |
| +-> c (50)
| +-> d (100)
|
+-> a (1)
+-> a (2)
+-> a (3)
+-> b (4)
Then valid path strings include (but are not limited to):
A.B - would get the value of node 'B[0]' (8) with parent node 'A[0]'
A.B.c - would get the value of node 'c[0]' (16) with parent node 'A[0].B[0]'
A.B.c[1] - would get the value of node 'c[1]' (32) with parent node 'A[0].B[0]'
A.B[1].c - would get the value of node 'c[0]' (64) with parent node 'A[0].B[1]'
A.B[1].c[1] - would get the value of node 'c[1]' (128) with parent node 'A[0].B[1]'
A.b[1] - would get the value of node 'b[1]' (10) with parent node 'A[0]'
B.c - would get the value of node 'c[0]' (50) with parent node 'B[0]'
a - would get the value of node 'a[0]' (1) with root parent node ''
a[2] - would get the value of node 'a[2]' (3) with root parent node ''
If this KVTree were to be iterated using ZKVTree::Iterator::Next(), a depth first
traversal of the structure would take place, giving us the following order:
A
A.B[0]
A.B[0].c[0]
A.B[0].c[1]
A.B[0].[0]
A.B[0].[1]
A.B[1].c
A.B[1].d
A.B[1].[0]
A.B[1].[1]
A.b[0]
A.b[1]
A.c
A.d
B
B.c
B.d
a[0]
a[1]
a[2]
A special syntax for accessing unnamed children is supported, which enables nested array behavior
for values:
A.B[0][0] - would get the value of (unnamed) node '[0]' (100) with parent node 'A.B[0]'
A.B[1][0] - would get the value of (unnamed) node '[0]' (10) with parent node 'A.B[0]'
A.B[1][1] - would get the value of (unnamed) node '[1]' (20) with parent node 'A.B[1]'
Note that these are syntactic sugar for:
A.B[0].[0] (could also be expressed as A.B[0]. or A.B. due to the implicit [0])
A.B[1].[0] (could also be expressed as A.B[1]. due to the implicit [0])
A.B[1].[1]
License:
TODO
*/
#ifndef _ZREGISTRY_H
#define _ZREGISTRY_H
#include <SST/SST_Hash.h>
#include <ZSTL/ZHashMap.hpp>
#include <ZSTL/ZString.hpp>
#include <ZSTL/ZBasicStringAlgo.hpp>
#include <ZUtil/ZConcurrency.hpp>
#include <ZUtil/ZSlabAllocator.hpp>
#include <stdarg.h>
//Valid path separator for ZKVTree
#ifndef ZKVTREE_PATH_SEPARATOR
#define ZKVTREE_PATH_SEPARATOR '.'
#endif
//Default number of nodes preallocated
#ifndef ZKVTREE_NODE_COUNT
#define ZKVTREE_NODE_COUNT (128)
#endif
//Invalid characters for a path ([] valid on subscript only)
#define ZKVTREE_INVALID_PATH_CHARS "%[]"
/*
ZKVTree class.
*/
class ZKVTree
{
public:
//KVTree node structure
struct Node
{
ZString Name; //Name of the node
ZString Value; //Value held by this node
Node* ParentNode; //Pointer to our parent node
size_t Index; //Index of this node in the parent's children array
ZArray< Node* > Children; //Children of this node
//Determines if this is the root node
bool IsRootNode() const {
if (ParentNode == NULL) {
return true;
} else return false;
}
//Finds the node with the given name in the child array
Node* GetChildByName(const ZString& _name, size_t _start, size_t _end, size_t _subscript) const {
for (size_t i = 0; i < Children.Size(); i++) {
Node* child = Children.Data()[i];
if (child->Name.Length() == (_end - _start)) {
if (ZStringAlgo::Equal(child->Name, 0, child->Name.Length(), _name, _start, _end)) {
if (_subscript-- == 0) {
return child;
}
}
}
}
return NULL;
}
//Finds the number of children with the given name
size_t GetChildCountByName(const ZString& _name, size_t _start, size_t _end) const {
size_t num = 0;
for (size_t i = 0; i < Children.Size(); i++) {
Node* child = Children.Data()[i];
if (child->Name.Length() == (_end - _start)) {
if (ZStringAlgo::Equal(child->Name, 0, child->Name.Length(), _name, _start, _end)) {
num++;
}
}
}
return num;
}
//Finds the index of the last occurrence of a node with the given name
size_t GetLastIndexByName(const ZString& _name, size_t _start, size_t _end) const {
size_t idx = 0;
for (size_t i = 0; i < Children.Size(); i++) {
Node* child = Children.Data()[i];
if (child->Name.Length() == (_end - _start)) {
if (ZStringAlgo::Equal(child->Name, 0, child->Name.Length(), _name, _start, _end)) {
idx = i;
}
}
}
return idx;
}
//Gets the path to this node
void GetPath(ZString& _out, Node* _end = NULL) const {
if (this != _end && ParentNode != NULL) {
ParentNode->GetPath(_out, _end);
} else return;
if (!_out.Empty()) {
_out.PushBack(ZKVTREE_PATH_SEPARATOR);
}
if (!IsRootNode()) {
size_t siblings = ParentNode->GetChildCountByName(Name, 0, Name.Length());
size_t last_idx = ParentNode->GetLastIndexByName(Name, 0, Name.Length());
_out += Name;
if (ParentNode != _end) {
ZString subscript;
ZStringAlgo::BuildPrintf(subscript, "[%i]", siblings - (1 + last_idx - Index));
_out += subscript;
}
}
}
// gets the simple path to this node (avoids [0] subscript)
void GetSimplePath(ZString& _out, Node* _end = NULL) const {
if (this != _end && ParentNode != NULL) {
ParentNode->GetSimplePath(_out, _end);
} else return;
if (!_out.Empty()) {
_out.PushBack(ZKVTREE_PATH_SEPARATOR);
}
if (!IsRootNode()) {
size_t siblings = ParentNode->GetChildCountByName(Name, 0, Name.Length());
size_t last_idx = ParentNode->GetLastIndexByName(Name, 0, Name.Length());
_out += Name;
if (ParentNode != _end && siblings - (1 + last_idx - Index) != 0) {
ZString subscript;
ZStringAlgo::BuildPrintf(subscript, "[%i]", siblings - (1 + last_idx - Index));
_out += subscript;
}
}
}
};
/*
KVTree iterator, used to directly iterate the nodes in the KVTree.
*/
class Iterator
{
friend class ZKVTree;
public:
/*
Parameterized Constructor.
@param _node - the node to start the iterator at
@param _tree - the KVTree we are iterating (reference needed for locking / unlocking)
*/
Iterator(Node* _node, ZKVTree* _tree)
: CurrentNode(_node), KVTree(_tree) { }
/*
Copy Constructor.
@param _other - the other iterator
*/
Iterator(const Iterator& _other)
:CurrentNode(_other.CurrentNode), KVTree(_other.KVTree) { }
/*
Destructor. Releases the lock (when 'Lock' is destructed).
*/
~Iterator() { }
/*
Gets the path to the current iterator location.
@return (ZString) - path to this node
*/
ZString GetPath() const
{ ZString path; CurrentNode->GetPath(path); return path; }
/*
Gets the path to the current iterator from an end node. If the end node
is not on the parent path, functionally equivalent to GetPath.
@param _end - node we should get path from
@return (ZString) - path to this node from provided node
*/
ZString GetPath(Iterator end) const
{ ZString path; CurrentNode->GetPath(path, end.CurrentNode); return path; }
/*
As GetPath, but avoids the [0] subscript.
@return (ZString) - path to this node
*/
ZString GetSimplePath() const
{ ZString path; CurrentNode->GetSimplePath(path); return path; }
/*
As GetPath, but avoids the [0] subscript.
@param _end - node we should get path from
@return (ZString) - path to this node from provided node
*/
ZString GetSimplePath(Iterator end) const
{ ZString path; CurrentNode->GetSimplePath(path, end.CurrentNode); return path; }
/*
Gets a reference to the name of the current node.
@return (const ZString&) - reference to the name of the current node
*/
const ZString& GetName() const
{ ZASSERT(!CurrentNode->IsRootNode(), "Cannot get name on root node!"); return CurrentNode->Name; }
/*
Gets a reference to the value of the current node.
@return (ZString&) - reference to the value of the current node
*/
const ZString& GetValue() const
{ ZASSERT(!CurrentNode->IsRootNode(), "Cannot get value on root node!"); return CurrentNode->Value; }
/*
Gets the index of the current node (sibling index).
@return (size_t) - the index of the current node.
*/
size_t GetIndex() const
{ return CurrentNode->Index; }
/*
Gets the number of children contained by this node.
*/
size_t GetChildCount() const
{ return CurrentNode->Children.Size(); }
/*
Gets the number of children of this node with the given name.
*/
size_t GetChildCountByName(const ZString& _name) const
{ return CurrentNode->GetChildCountByName(_name, 0, _name.Length()); }
/*
Gets the index of the nth child of the given name (ZSTL::InvalidPos if not found).
*/
size_t GetChildIndexByName(const ZString& _name, size_t _n = 0) const
{
Node* childNode = CurrentNode->GetChildByName(_name, 0, _name.Length(), _n);
return (childNode == NULL ? ZSTL::InvalidPos : childNode->Index);
}
/*
Gets the number of siblings to the current node, including the current node.
*/
size_t GetSiblingCount() const
{
if (CurrentNode->ParentNode == NULL)
return 0;
return CurrentNode->ParentNode->Children.Size();
}
/*
Gets the number of siblings to the current node with the given name (can
include this node if name is equivalent).
*/
size_t GetSiblingCountByName(const ZString& _name) const
{
if (CurrentNode->ParentNode == NULL)
return 0;
return CurrentNode->ParentNode->GetChildCountByName(_name, 0, _name.Length());
}
/*
Gets the index of the nth sibling of the given name (ZSTL::InvalidPos if not found, can
be the node this iterator points to).
*/
size_t GetSiblingIndexByName(const ZString& _name, size_t _n = 0) const
{
if (CurrentNode->ParentNode == NULL)
return ZSTL::InvalidPos;
Node* siblingNode = CurrentNode->ParentNode->GetChildByName(_name, 0, _name.Length(), _n);
return (siblingNode == NULL ? ZSTL::InvalidPos : siblingNode->Index);
}
//Checks to see if this is an end node
bool CheckEnd()
{ return CurrentNode->IsRootNode(); }
//Checks to see if we can move to the parent node
bool CheckParent()
{ return CurrentNode->ParentNode != NULL; }
//Checks to see if we can move to the child node
bool CheckChild(size_t _index = 0)
{ return _index < CurrentNode->Children.Size(); }
//Checks to see if we can move to the sibling node
bool CheckSibling(size_t _index = 0)
{ return CurrentNode->ParentNode != NULL && _index < CurrentNode->ParentNode->Children.Size() - 1; }
//Checks to see if we can move to the next sibling node
bool CheckNextSibling()
{ return CurrentNode->ParentNode != NULL && CurrentNode->Index < CurrentNode->ParentNode->Children.Size() - 1; }
//Checks to see if we can move to the previous sibling node
bool CheckPrevSibling()
{ return CurrentNode->Index > 0; }
/*
Sets the value of the current node.
@param (ZString&) - value to set this node to
*/
void SetValue(const ZString& _value)
{ CurrentNode->Value = _value; }
/*
public ZKVTree::Iterator::Next
This operation attempts to move this iterator to the first child of this node. If this
is an invalid move, then it attempts to iterate to the next sibling. If this is invalid,
it will attempt to iterate to the next sibling of the parent, and will continue to
attempt this until it has either reached the end or it can do so.
This basically means using nothing but 'Next' from beginning to end will result in
a depth-first traversal of the KVTree.
@return (Iterator&) - this iterator
*/
Iterator& Next()
{
if (CheckChild())
Child();
else if (CheckNextSibling())
NextSibling();
else {
while (CurrentNode->ParentNode != NULL) {
if (CheckParent()) {
Parent();
if (CheckNextSibling()) {
NextSibling();
break;
}
}
}
}
return *this;
}
/*
public ZKVTree::Iterator::Parent
This operation attempts to move the iterator to the parent node of this node.
@return (Iterator&) - this iterator
*/
Iterator& Parent()
{
if (CheckParent()) {
CurrentNode = CurrentNode->ParentNode;
} else *this = KVTree->End();
return *this;
}
/*
public ZKVTree::Iterator::Child
This operation moves the iterator to child node at the given index.
@param _index - the child number to move to
@return (Iterator&) - this iterator
*/
Iterator& Child(size_t _index = 0)
{
if (CheckChild(_index)) {
CurrentNode = CurrentNode->Children[_index];
} else *this = KVTree->End();
return *this;
}
/*
public ZKVTree::Iterator::Sibling
This operation moves the iterator to the sibling node at the given index.
@param _index - the sibling index
@return (Iterator&) - this iterator
*/
Iterator& Sibling(size_t _index = 0)
{
ZASSERT(CheckSibling(_index), "ZKVTree::Iterator unable to move to sibling node!");
if (CheckSibling(_index)) {
CurrentNode = CurrentNode->ParentNode->Children[_index];
} else *this = KVTree->End();
return *this;
}
/*
public ZKVTree::Iterator::PrevSibling
This operation moves the iterator to the previous sibling node of this node.
@return (Iterator&) - this iterator
*/
Iterator& PrevSibling()
{
if (CheckPrevSibling()) {
CurrentNode = CurrentNode->ParentNode->Children[CurrentNode->Index - 1];
} else *this = KVTree->End();
return *this;
}
/*
public ZKVTree::Iterator::NextSibling
This operation moves the iterator to the next sibling node of this node.
@return (Iterator&) - this iterator
*/
Iterator& NextSibling()
{
if (CheckNextSibling()) {
CurrentNode = CurrentNode->ParentNode->Children[CurrentNode->Index + 1];
} else *this = KVTree->End();
return *this;
}
/*
public ZKVTree::Iterator::ParentItr
This operation returns an iterator to the parent node.
@return (ZKVTree::Iterator) - iterator to the parent node
*/
Iterator ParentItr() const
{
ZASSERT(CurrentNode->ParentNode != NULL, "Unable to get KVTree parent!");
return Iterator(CurrentNode->ParentNode, KVTree);
}
/*
public ZKVTree::Iterator::ChildItr
This operation returns an iterator to the first child node of this iterator,
or the end iterator if no child is found.
@param _index - the child node index to get an iterator to
@return (Iterator) - iterator to the child node
*/
Iterator ChildItr() const
{
return ChildItr(0);
}
/*
public ZKVTree::Iterator::ChildItr
This operation returns an iterator to the child node at the given index.
@param _index - the child node index to get an iterator to
@return (Iterator) - iterator to the child node
*/
Iterator ChildItr(size_t _index) const
{
if (_index == ZSTL::InvalidPos || _index >= CurrentNode->Children.Size()) {
return KVTree->End();
} else return Iterator(CurrentNode->Children.Data()[_index], KVTree);
}
/*
public ZKVTree::Iterator::ChildItr
This returns an iterator to the nth child of the given name.
@param _name - the name of the child to get an iterator to
@param _n - the occurrence of the child to get (0 = first occurrence)
@return (Iterator) - iterator to the child node
*/
Iterator ChildItr(const ZString& name, size_t _n = 0) const
{
return ChildItr(GetChildIndexByName(name, _n));
}
/*
public ZKVTree::Iterator::GetSibling
This operation returns an iterator to the sibling node at the given index.
@param _index - the child node index to get an iterator to
@return (Iterator) - iterator to the child node
*/
Iterator SiblingItr(size_t _index) const
{
if (_index == ZSTL::InvalidPos || _index >= CurrentNode->ParentNode->Children.Size()) {
return KVTree->End();
} else return Iterator(CurrentNode->ParentNode->Children.Data()[_index], KVTree);
}
/*
public ZKVTree::Iterator::GetSibling
This operation returns an iterator to the nth sibling of the given name.
@param _name - the name of the sibling to get an iterator to
@param _n - the occurrence of the sibling to get (0 = first occurrence)
@return (Iterator) - iterator to the sibling node
*/
Iterator SiblingItr(const ZString& name, size_t _n = 0) const
{
return SiblingItr(GetSiblingIndexByName(name, _n));
}
//Operator Overloads
Iterator& operator ++ () { return Next(); }
Iterator operator ++ (int) { Iterator itr = *this; Next(); return itr; }
Iterator& operator = (const Iterator& _other) { CurrentNode = _other.CurrentNode; KVTree = _other.KVTree; return *this; }
bool operator == (const Iterator& _other) const { return CurrentNode == _other.CurrentNode; }
bool operator != (const Iterator& _other) const { return !(*this == _other); }
private:
Node* CurrentNode; //Our current KVTree node
ZKVTree* KVTree; //The KVTree we are iterating
};
/*
Constructor.
*/
ZKVTree();
/*
Copy Constructor.
*/
ZKVTree(const ZKVTree& _other);
/*
Destructor.
*/
~ZKVTree();
/*
= operator. Performs a copy of node names and data.
@param _other - the KVTree to copy from
*/
ZKVTree& operator = (const ZKVTree& _other);
/*
public ZKVTree::Add
Adds a node to the tree under the given parent node. The version that takes no parent path
will add the child under the root node. The iterator returned is used to directly address the
node.
The two parameter version of this function adds children to the root node.
@param _parent - the path to the parent node
@param _itr - iterator to the parent node
@param _name - the name of the child node
@param _value - the value of the child node
@return (Iterator) - iterator to the created node
*/
Iterator Add(const ZString& _name, const ZString& _value);
Iterator Add(const ZString& _parent, const ZString& _name, const ZString& _value);
Iterator Add(const Iterator& _itr, const ZString& _name, const ZString& _value);
/*
public ZKVTree::Begin
Gets a ZKVTree::Iterator to the first child of the root node of this KVTree. If there
are no children, returns ZKVTree::End().
@return (ZKVTree::Iterator) - iterator to the beginning of the KVTree (first child node)
*/
Iterator Begin() const;
/*
public ZKVTree::Clear
Clears the KVTree of all values.
@return (void)
*/
void Clear();
/*
public ZKVTree::End
Gets a ZKVTree::Iterator to the end of the KVTree.
@return (ZKVTree::Iterator) - iterator to the end of the KVTree
*/
const Iterator End() const;
/*
public ZKVTree::Erase
Erases the node at the given position and all children.
@param _path - the path to the node
@param _itr - iterator to the node (invalidated by this call)
@return (void)
*/
void Erase(const ZString& _path);
void Erase(const Iterator& _itr);
/*
public ZKVTree::Find
Gets a ZKVTree::Iterator to the given element if found. Returns an iterator
equivalent to ZKVTree::End() if not found.
@param _path - path to the node to get an iterator to
@param _itr - parent iterator to search from (path will be searched from this node as root)
@return (ZKVTree::Iterator) - iterator to value if found, End() otherwise
*/
Iterator Find(const ZString& _path) const;
Iterator Find(const Iterator& _itr, const ZString& _path) const;
/*
public ZKVTree::Get
Gets a value from the KVTree that held by the node at the provided path.
@param _path - the path to the node to get the value from (if no subscript, [0] is assumed)
@return (const ZString&) - reference to the value held by the node at the provided path
@assert - if no value exists at the provided path
*/
const ZString& Get(const ZString& _path) const;
const ZString& Get(const Iterator& _itr, const ZString& _path);
/*
public ZKVTree::Merge
Merges this KVTree with another, bringing in the values defined in the other
KVTree.
@param _other - the KVTree to merge values from
@param _overwrite - if true, values will be overwritten when a conflict occurs
@return (void)
*/
void Merge(const ZKVTree& _other, bool _overwrite);
/*
public ZKVTree::Put
Puts a node with the given value into the KVTree at the provided path. Will overwrite
any existing value at that location.
If the path string has a child node that does not exist, it will be created so long as the
subscript is for the next child node, i.e., if A.B.c[1] is passed in, it will be created so
long as A.B.c[0] exists.
The iterator version overwrites the data at that location if the iterator was created by this
tree, and will attempt to create it as above if not from this tree.
@param _path - the path to the KVTree value (if no index, [0] is assumed)
@param _itr - iterator to the node to assign the value to (will overwrite)
@param _value - the value to assign to the node
@return (Iterator) - iterator to the created value
*/
Iterator Put(const ZString& _key, const ZString& _value);
Iterator Put(const Iterator& _itr, const ZString& _value);
/*
public ZKVTree::IsValidKeyName
Checks that a key name is able to be put into the KVTree.
This basically ensures that a key will be valid for use in pathing.
A valid key name doesn't contain whitespace, square brackets, or periods.
@param _name - the name being considered for validity.
@return (bool) - true if the keyname would be valid in a path, false otherwise
*/
bool IsValidKeyName( const ZString& _key ) const;
protected:
ZSlabAllocator<Node, ZKVTREE_NODE_COUNT> NodeAllocator;
Node RootNode; // The root node of the KVTree (also acts as the end node)
//Gets a node from the KVTree given a path (NULL if not found)
Node* GetFromPath(const ZString& _path) const;
//Creates all the nodes needed along a path to be able to get and set the value, returning the node
Node* CreatePath(const ZString& _path);
//Helper function used to check in all nodes
void NodeCheckIn(Node* _node);
};
#endif