Initial commit
This commit is contained in:
547
ZUtil/ZKVTree.cpp
Normal file
547
ZUtil/ZKVTree.cpp
Normal file
@@ -0,0 +1,547 @@
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user