548 lines
14 KiB
C++
548 lines
14 KiB
C++
#include <ZUtil/ZKVTree.hpp>
|
|
#include <ZUtil/ZLog.hpp>
|
|
|
|
#include <ZSTL/ZBasicStringAlgo.hpp>
|
|
#include <ZSTL/ZArrayAlgo.hpp>
|
|
|
|
/*************************************************************************/
|
|
|
|
void ZKVTree::NodeCheckIn(Node* _node)
|
|
{
|
|
for (size_t i = 0; i < _node->Children.Size(); i++)
|
|
NodeCheckIn(_node->Children[i]);
|
|
|
|
_node->Children.Clear();
|
|
|
|
NodeAllocator.Deallocate(_node);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZKVTree::ZKVTree()
|
|
: RootNode()
|
|
{
|
|
RootNode.ParentNode = NULL;
|
|
RootNode.Index = 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZKVTree::ZKVTree( const ZKVTree& _other )
|
|
: RootNode()
|
|
{
|
|
RootNode.ParentNode = NULL;
|
|
RootNode.Index = 0;
|
|
|
|
for (ZKVTree::Iterator itr = _other.Begin(); itr != _other.End(); ++itr) {
|
|
Put(itr, itr.GetValue());
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZKVTree::~ZKVTree()
|
|
{
|
|
for (size_t i = 0; i < RootNode.Children.Size(); i++)
|
|
NodeCheckIn(RootNode.Children[i]);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZKVTree& ZKVTree::operator = ( const ZKVTree& _other )
|
|
{
|
|
Clear();
|
|
|
|
for (ZKVTree::Iterator itr = _other.Begin(); itr != _other.End(); ++itr) {
|
|
Put(itr.GetPath(), itr.GetValue());
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
// This will convert to a full path, with subscripts everywhere and path separators. EXAMPLES:
|
|
//
|
|
// A.B[2].C.d -> A[0].B[2].C[0].d[0]
|
|
// A.B[0][1] -> A[0].B[0].[1]
|
|
//
|
|
static bool ConvertPath(const ZString& _path, ZString& _out)
|
|
{
|
|
const ZString zeroSubscript("[0]");
|
|
|
|
size_t i = 0;
|
|
size_t j = 0;
|
|
size_t k = 0;
|
|
|
|
//We are going to loop, looking for path separator and making sure we have subscripts and proper path separators
|
|
while (i != ZSTL::InvalidPos) {
|
|
// find the next path separator or the first occurrence of a nested array access
|
|
j = ZStringAlgo::FindFirst(_path, ZKVTREE_PATH_SEPARATOR, i, _path.Length());
|
|
k = ZStringAlgo::FindSub(_path, i, _path.Length(), "][", 0, 2);
|
|
|
|
// if a nested array access occurs
|
|
if (k < j) {
|
|
// append the first part of it, a path separators, and then process from there
|
|
ZStringAlgo::Append(_out, _path, i, k + 1);
|
|
|
|
_out.PushBack(ZKVTREE_PATH_SEPARATOR);
|
|
|
|
i = k + 1;
|
|
} else if (i == j) {
|
|
// this means we are dealing with a node without a name (potentially first node)
|
|
if (_out.Empty() || _out.Back() != ']')
|
|
ZStringAlgo::Append(_out, zeroSubscript);
|
|
|
|
_out.PushBack(ZKVTREE_PATH_SEPARATOR);
|
|
|
|
i = j + 1;
|
|
} else if (j != ZSTL::InvalidPos) {
|
|
// found a substring between two path separators
|
|
ZStringAlgo::Append(_out, _path, i, j);
|
|
|
|
if (_out.Back() != ']')
|
|
ZStringAlgo::Append(_out, zeroSubscript);
|
|
|
|
_out.PushBack(ZKVTREE_PATH_SEPARATOR);
|
|
|
|
i = j + 1;
|
|
} else {
|
|
// we have reached the end
|
|
ZStringAlgo::Append(_out, _path, i, _path.Length());
|
|
|
|
if (_out.Empty() || _out.Back() != ']')
|
|
ZStringAlgo::Append(_out, zeroSubscript);
|
|
|
|
i = ZSTL::InvalidPos;
|
|
}
|
|
}
|
|
|
|
// this method may be expanded later for path verification via regex or some such
|
|
return true;
|
|
}
|
|
|
|
//This will insert a child node at the given index and increment the indices on the other child values
|
|
static void InsertNode( ZKVTree::Node* _parent, ZKVTree::Node* _child, size_t _index )
|
|
{
|
|
_parent->Children.Insert(_index, _child);
|
|
|
|
for (size_t i = _index + 1; i < _parent->Children.Size(); i++)
|
|
_parent->Children.Data()[i]->Index++;
|
|
}
|
|
|
|
//This will erase a child node at the given index and decrement the indices on the other child values
|
|
static ZKVTree::Node* EraseChildNode( ZKVTree::Node* _parent, size_t _index )
|
|
{
|
|
for (size_t i = _index + 1; i < _parent->Children.Size(); i++)
|
|
_parent->Children.Data()[i]->Index--;
|
|
|
|
return _parent->Children.Erase(_index);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
// gets the value from a completed path (returned from ConvertFullPath)
|
|
ZKVTree::Node* ZKVTree::GetFromPath(const ZString& _path) const
|
|
{
|
|
size_t i, j, k;
|
|
size_t subscript;
|
|
|
|
const Node* node = &RootNode;
|
|
|
|
// path[i...j] is our path window (current value we are looking for)
|
|
// path[subscript].nextpath
|
|
// ^ ^ ^
|
|
// i j k
|
|
// or
|
|
// path.nextpath
|
|
// ^ ^
|
|
// i j,k
|
|
i = 0;
|
|
j = 0;
|
|
k = 0;
|
|
|
|
//While we still have a node to look in
|
|
while (node != NULL)
|
|
{
|
|
//See if we have reached the end
|
|
if (i >= _path.Length())
|
|
break;
|
|
|
|
subscript = 0;
|
|
|
|
j = ZStringAlgo::FindFirst(_path, ZKVTREE_PATH_SEPARATOR, i, _path.Length());
|
|
|
|
//If we can't find a path separator, go to the end
|
|
if (j == ZSTL::InvalidPos)
|
|
j = _path.Length();
|
|
|
|
//See if we have found the node
|
|
if (i >= j)
|
|
break;
|
|
|
|
k = j;
|
|
|
|
//Determine if it has a subscript
|
|
if (_path[j - 1] == ']')
|
|
{
|
|
j = ZStringAlgo::FindLast(_path, '[', i, j);
|
|
|
|
//Make sure this is bounded correct
|
|
if (j == ZSTL::InvalidPos || j < i)
|
|
{
|
|
node = NULL;
|
|
break;
|
|
}
|
|
|
|
subscript = (size_t)ZStringAlgo::NumericInt(_path, j + 1, k - 1);
|
|
}
|
|
|
|
node = node->GetChildByName(_path, i, j, subscript);
|
|
|
|
//Move up our path 'window'
|
|
i = k + 1;
|
|
}
|
|
|
|
return const_cast<ZKVTree::Node*>(node);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZKVTree::Node* ZKVTree::CreatePath(const ZString& _path)
|
|
{
|
|
size_t i, j, k;
|
|
size_t subscript;
|
|
|
|
Node* node = &RootNode;
|
|
|
|
// path[i...j] is our path window (current value we are looking for)
|
|
// path[subscript].nextpath
|
|
// ^ ^ ^
|
|
// i j k
|
|
// or
|
|
// path.nextpath
|
|
// ^ ^
|
|
// i j,k
|
|
i = 0;
|
|
j = 0;
|
|
k = 0;
|
|
|
|
// while we still have a node to look in
|
|
while (node != NULL) {
|
|
// see if we have reached the end
|
|
if (i >= _path.Length()) {
|
|
break;
|
|
}
|
|
|
|
subscript = 0;
|
|
|
|
j = ZStringAlgo::FindFirst(_path, ZKVTREE_PATH_SEPARATOR, i, _path.Length());
|
|
|
|
//If we can't find a path separator, go to the end
|
|
if (j == ZSTL::InvalidPos) {
|
|
j = _path.Length();
|
|
}
|
|
|
|
//See if we are done creating
|
|
if (i == j) {
|
|
break;
|
|
}
|
|
|
|
k = j;
|
|
|
|
// determine if it has a subscript
|
|
if (_path[j - 1] == ']') {
|
|
j = ZStringAlgo::FindLast(_path, '[', i, j);
|
|
|
|
// make sure this is bounded correct (if not, bail out)
|
|
if (j == ZSTL::InvalidPos || j < i) {
|
|
node = NULL;
|
|
break;
|
|
}
|
|
|
|
subscript = (size_t)ZStringAlgo::NumericInt(_path, j + 1, k - 1);
|
|
}
|
|
|
|
Node* childNode = node->GetChildByName(_path, i, j, subscript);
|
|
|
|
// create this node if it doesn't exist
|
|
if (childNode == NULL) {
|
|
// get the child count by name at the parent node
|
|
size_t count = node->GetChildCountByName(_path, i, j);
|
|
|
|
// see if the subscript at the end is the 'next' value (if not, bail out)
|
|
if (count != subscript) {
|
|
node = NULL;
|
|
break;
|
|
}
|
|
|
|
// get a new node, set the parent node, name, index (leave out the value)
|
|
childNode = NodeAllocator.Allocate();
|
|
|
|
childNode->ParentNode = node;
|
|
childNode->Name.Resize(j - i);
|
|
ZStringAlgo::Copy(childNode->Name, 0, childNode->Name.Length(), _path, i, j);
|
|
|
|
if (count > 0) {
|
|
childNode->Index = node->GetLastIndexByName(_path, i, j) + 1;
|
|
|
|
// increment the index on the rest of the nodes
|
|
for (size_t i = childNode->Index; i < node->Children.Size(); i++)
|
|
node->Children[i]->Index++;
|
|
|
|
node->Children.Insert(childNode->Index, childNode);
|
|
} else {
|
|
childNode->Index = node->Children.Size();
|
|
node->Children.PushBack(childNode);
|
|
}
|
|
}
|
|
|
|
// set our new node
|
|
node = childNode;
|
|
|
|
// move up our 'path window'
|
|
i = k + 1;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZKVTree::Iterator ZKVTree::Add( const ZString& _name, const ZString& _value )
|
|
{
|
|
return Add(End(), _name, _value);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZKVTree::Iterator ZKVTree::Add( const ZString& _parent, const ZString& _name, const ZString& _value )
|
|
{
|
|
ZString path;
|
|
|
|
if (!ConvertPath(_parent, path))
|
|
return End();
|
|
|
|
Node* node = CreatePath(path);
|
|
|
|
return Add(Iterator(node, this), _name, _value);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZKVTree::Iterator ZKVTree::Add( const Iterator& _itr, const ZString& _name, const ZString& _value )
|
|
{
|
|
ZKVTree::Node* parentNode = NULL;
|
|
ZKVTree::Node* childNode = NULL;
|
|
|
|
if (_itr.CurrentNode == NULL) {
|
|
return End();
|
|
}
|
|
|
|
if (_itr.KVTree == this) {
|
|
parentNode = _itr.CurrentNode;
|
|
|
|
// allocate a child node and set values
|
|
childNode = NodeAllocator.Allocate();
|
|
childNode->ParentNode = parentNode;
|
|
childNode->Name = _name;
|
|
childNode->Value = _value;
|
|
|
|
// determine if any other children of the same name exist (grouped on insert)
|
|
size_t count = parentNode->GetChildCountByName(_name, 0, _name.Length());
|
|
|
|
if (count > 0) {
|
|
childNode->Index = parentNode->GetLastIndexByName(_name, 0, _name.Length()) + 1;
|
|
} else {
|
|
childNode->Index = parentNode->Children.Size();
|
|
}
|
|
|
|
InsertNode(parentNode, childNode, childNode->Index);
|
|
|
|
return Iterator(childNode, this);
|
|
} else {
|
|
// if this is from another tree, just add at the path
|
|
return Add(_itr.GetPath(), _name, _value);
|
|
}
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
|
|
void ZKVTree::Clear()
|
|
{
|
|
for (size_t i = 0; i < RootNode.Children.Size(); i++) {
|
|
NodeCheckIn(RootNode.Children.Data()[i]);
|
|
}
|
|
|
|
RootNode.Children.Clear();
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
void ZKVTree::Erase( const ZString& _path )
|
|
{
|
|
Erase(Find(_path));
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
void ZKVTree::Erase( const Iterator& _itr )
|
|
{
|
|
ZASSERT_RUNTIME(_itr.CurrentNode != NULL, "ZKVTree: Unable to erase value from provided iterator!");
|
|
ZASSERT_RUNTIME(_itr.CurrentNode != &RootNode, "ZKVTree: Unable to erase root node!");
|
|
|
|
if (_itr.KVTree == this) {
|
|
NodeCheckIn(EraseChildNode(_itr.CurrentNode->ParentNode, _itr.CurrentNode->Index));
|
|
} else {
|
|
Erase(_itr.GetPath());
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZKVTree::Iterator ZKVTree::Find( const ZString& _path ) const
|
|
{
|
|
ZString path;
|
|
|
|
if (!ConvertPath(_path, path)) {
|
|
return End();
|
|
}
|
|
|
|
Node* node = GetFromPath(path);
|
|
|
|
if (node == NULL) {
|
|
return End();
|
|
}
|
|
|
|
return ZKVTree::Iterator(node, const_cast<ZKVTree*>(this));
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZKVTree::Iterator ZKVTree::Find( const Iterator& _itr, const ZString& _path ) const
|
|
{
|
|
ZString path;
|
|
|
|
if (!ConvertPath(_path, path)) {
|
|
return End();
|
|
}
|
|
|
|
Node* node = GetFromPath(_itr.GetPath() + ZKVTREE_PATH_SEPARATOR + path);
|
|
|
|
if (node == NULL) {
|
|
return End();
|
|
}
|
|
|
|
return ZKVTree::Iterator(node, const_cast<ZKVTree*>(this));
|
|
|
|
}
|
|
/*************************************************************************/
|
|
|
|
const ZString& ZKVTree::Get( const ZString& _path ) const
|
|
{
|
|
ZKVTree::Iterator itr = Find(_path);
|
|
|
|
ZASSERT_RUNTIME(itr != End(), "ZKVTree: No value mapped to path!");
|
|
|
|
return itr.GetValue();
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
const ZString& ZKVTree::Get( const Iterator& _itr, const ZString& _path )
|
|
{
|
|
ZKVTree::Iterator itr = Find(_itr, _path);
|
|
|
|
ZASSERT_RUNTIME(itr != End(), "ZKVTree: No value mapped to path!");
|
|
|
|
return itr.GetValue();
|
|
}
|
|
/*************************************************************************/
|
|
|
|
void ZKVTree::Merge( const ZKVTree& _other, bool _overwrite )
|
|
{
|
|
for (ZKVTree::Iterator itr = _other.Begin(); itr != _other.End(); ++itr) {
|
|
if (_overwrite) {
|
|
Put(itr.GetPath(), itr.GetValue());
|
|
} else {
|
|
ZKVTree::Iterator found = Find(itr.GetPath());
|
|
if (found == End()) {
|
|
Put(itr.GetPath(), itr.GetValue());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZKVTree::Iterator ZKVTree::Put( const ZString& _path, const ZString& _value )
|
|
{
|
|
ZString path;
|
|
|
|
if (!ConvertPath(_path, path))
|
|
return End();
|
|
|
|
// create the node at the path given (will return the existing node if there)
|
|
Node* node = CreatePath(path);
|
|
|
|
// if we were able to successfully create the node, set its value
|
|
if (node != NULL) {
|
|
node->Value = _value;
|
|
return Iterator(node, this);
|
|
} else return End();
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZKVTree::Iterator ZKVTree::Put(const Iterator& _itr, const ZString& _value)
|
|
{
|
|
// ensure this iterator belongs to us
|
|
if (_itr.KVTree == this) {
|
|
// it does, just set the value
|
|
_itr.CurrentNode->Value = _value;
|
|
return _itr;
|
|
} else {
|
|
// it does not, just create a new value using the path variant
|
|
return Put(_itr.GetPath(), _value);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZKVTree::Iterator ZKVTree::Begin() const
|
|
{
|
|
if (RootNode.Children.Empty()) {
|
|
return End();
|
|
} else return ZKVTree::Iterator(const_cast<Node*>(RootNode.Children.Front()), const_cast<ZKVTree*>(this));
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
const ZKVTree::Iterator ZKVTree::End() const
|
|
{
|
|
return ZKVTree::Iterator(const_cast<Node*>(&RootNode), const_cast<ZKVTree*>(this));
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
static bool isInvalidKeyNameChar(const char _char)
|
|
{
|
|
return ( isspace(_char) != 0) ||
|
|
( iscntrl(_char) != 0) ||
|
|
( _char == ZKVTREE_PATH_SEPARATOR ) ||
|
|
( _char == '[' ) ||
|
|
( _char == ']' );
|
|
}
|
|
|
|
bool ZKVTree::IsValidKeyName( const ZString& _name ) const
|
|
{
|
|
for (size_t i = 0; i < _name.Length(); i++) {
|
|
if (isInvalidKeyNameChar( _name.Data()[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|