mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-27 06:25:51 +00:00
659 lines
17 KiB
C++
659 lines
17 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
Copyright (c) 2011-2013, OpenCoin, Inc.
|
|
*/
|
|
//==============================================================================
|
|
|
|
// included by json_value.cpp
|
|
// everything is within Json namespace
|
|
|
|
// //////////////////////////////////////////////////////////////////
|
|
// //////////////////////////////////////////////////////////////////
|
|
// //////////////////////////////////////////////////////////////////
|
|
// class ValueInternalMap
|
|
// //////////////////////////////////////////////////////////////////
|
|
// //////////////////////////////////////////////////////////////////
|
|
// //////////////////////////////////////////////////////////////////
|
|
|
|
/** \internal MUST be safely initialized using memset( this, 0, sizeof(ValueInternalLink) );
|
|
* This optimization is used by the fast allocator.
|
|
*/
|
|
ValueInternalLink::ValueInternalLink ()
|
|
: previous_ ( 0 )
|
|
, next_ ( 0 )
|
|
{
|
|
}
|
|
|
|
ValueInternalLink::~ValueInternalLink ()
|
|
{
|
|
for ( int index = 0; index < itemPerLink; ++index )
|
|
{
|
|
if ( !items_[index].isItemAvailable () )
|
|
{
|
|
if ( !items_[index].isMemberNameStatic () )
|
|
free ( keys_[index] );
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ValueMapAllocator::~ValueMapAllocator ()
|
|
{
|
|
}
|
|
|
|
#ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR
|
|
class DefaultValueMapAllocator : public ValueMapAllocator
|
|
{
|
|
public: // overridden from ValueMapAllocator
|
|
virtual ValueInternalMap* newMap ()
|
|
{
|
|
return new ValueInternalMap ();
|
|
}
|
|
|
|
virtual ValueInternalMap* newMapCopy ( const ValueInternalMap& other )
|
|
{
|
|
return new ValueInternalMap ( other );
|
|
}
|
|
|
|
virtual void destructMap ( ValueInternalMap* map )
|
|
{
|
|
delete map;
|
|
}
|
|
|
|
virtual ValueInternalLink* allocateMapBuckets ( unsigned int size )
|
|
{
|
|
return new ValueInternalLink[size];
|
|
}
|
|
|
|
virtual void releaseMapBuckets ( ValueInternalLink* links )
|
|
{
|
|
delete [] links;
|
|
}
|
|
|
|
virtual ValueInternalLink* allocateMapLink ()
|
|
{
|
|
return new ValueInternalLink ();
|
|
}
|
|
|
|
virtual void releaseMapLink ( ValueInternalLink* link )
|
|
{
|
|
delete link;
|
|
}
|
|
};
|
|
#else
|
|
/// @todo make this thread-safe (lock when accessign batch allocator)
|
|
class DefaultValueMapAllocator : public ValueMapAllocator
|
|
{
|
|
public: // overridden from ValueMapAllocator
|
|
virtual ValueInternalMap* newMap ()
|
|
{
|
|
ValueInternalMap* map = mapsAllocator_.allocate ();
|
|
new (map) ValueInternalMap (); // placement new
|
|
return map;
|
|
}
|
|
|
|
virtual ValueInternalMap* newMapCopy ( const ValueInternalMap& other )
|
|
{
|
|
ValueInternalMap* map = mapsAllocator_.allocate ();
|
|
new (map) ValueInternalMap ( other ); // placement new
|
|
return map;
|
|
}
|
|
|
|
virtual void destructMap ( ValueInternalMap* map )
|
|
{
|
|
if ( map )
|
|
{
|
|
map->~ValueInternalMap ();
|
|
mapsAllocator_.release ( map );
|
|
}
|
|
}
|
|
|
|
virtual ValueInternalLink* allocateMapBuckets ( unsigned int size )
|
|
{
|
|
return new ValueInternalLink[size];
|
|
}
|
|
|
|
virtual void releaseMapBuckets ( ValueInternalLink* links )
|
|
{
|
|
delete [] links;
|
|
}
|
|
|
|
virtual ValueInternalLink* allocateMapLink ()
|
|
{
|
|
ValueInternalLink* link = linksAllocator_.allocate ();
|
|
memset ( link, 0, sizeof (ValueInternalLink) );
|
|
return link;
|
|
}
|
|
|
|
virtual void releaseMapLink ( ValueInternalLink* link )
|
|
{
|
|
link->~ValueInternalLink ();
|
|
linksAllocator_.release ( link );
|
|
}
|
|
private:
|
|
BatchAllocator<ValueInternalMap, 1> mapsAllocator_;
|
|
BatchAllocator<ValueInternalLink, 1> linksAllocator_;
|
|
};
|
|
#endif
|
|
|
|
static ValueMapAllocator*& mapAllocator ()
|
|
{
|
|
static DefaultValueMapAllocator defaultAllocator;
|
|
static ValueMapAllocator* mapAllocator = &defaultAllocator;
|
|
return mapAllocator;
|
|
}
|
|
|
|
static struct DummyMapAllocatorInitializer
|
|
{
|
|
DummyMapAllocatorInitializer ()
|
|
{
|
|
mapAllocator (); // ensure mapAllocator() statics are initialized before main().
|
|
}
|
|
} dummyMapAllocatorInitializer;
|
|
|
|
|
|
|
|
// h(K) = value * K >> w ; with w = 32 & K prime w.r.t. 2^32.
|
|
|
|
/*
|
|
use linked list hash map.
|
|
buckets array is a container.
|
|
linked list element contains 6 key/values. (memory = (16+4) * 6 + 4 = 124)
|
|
value have extra state: valid, available, deleted
|
|
*/
|
|
|
|
|
|
ValueInternalMap::ValueInternalMap ()
|
|
: buckets_ ( 0 )
|
|
, tailLink_ ( 0 )
|
|
, bucketsSize_ ( 0 )
|
|
, itemCount_ ( 0 )
|
|
{
|
|
}
|
|
|
|
|
|
ValueInternalMap::ValueInternalMap ( const ValueInternalMap& other )
|
|
: buckets_ ( 0 )
|
|
, tailLink_ ( 0 )
|
|
, bucketsSize_ ( 0 )
|
|
, itemCount_ ( 0 )
|
|
{
|
|
reserve ( other.itemCount_ );
|
|
IteratorState it;
|
|
IteratorState itEnd;
|
|
other.makeBeginIterator ( it );
|
|
other.makeEndIterator ( itEnd );
|
|
|
|
for ( ; !equals (it, itEnd); increment (it) )
|
|
{
|
|
bool isStatic;
|
|
const char* memberName = key ( it, isStatic );
|
|
const Value& aValue = value ( it );
|
|
resolveReference (memberName, isStatic) = aValue;
|
|
}
|
|
}
|
|
|
|
|
|
ValueInternalMap&
|
|
ValueInternalMap::operator = ( const ValueInternalMap& other )
|
|
{
|
|
ValueInternalMap dummy ( other );
|
|
swap ( dummy );
|
|
return *this;
|
|
}
|
|
|
|
|
|
ValueInternalMap::~ValueInternalMap ()
|
|
{
|
|
if ( buckets_ )
|
|
{
|
|
for ( BucketIndex bucketIndex = 0; bucketIndex < bucketsSize_; ++bucketIndex )
|
|
{
|
|
ValueInternalLink* link = buckets_[bucketIndex].next_;
|
|
|
|
while ( link )
|
|
{
|
|
ValueInternalLink* linkToRelease = link;
|
|
link = link->next_;
|
|
mapAllocator ()->releaseMapLink ( linkToRelease );
|
|
}
|
|
}
|
|
|
|
mapAllocator ()->releaseMapBuckets ( buckets_ );
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ValueInternalMap::swap ( ValueInternalMap& other )
|
|
{
|
|
ValueInternalLink* tempBuckets = buckets_;
|
|
buckets_ = other.buckets_;
|
|
other.buckets_ = tempBuckets;
|
|
ValueInternalLink* tempTailLink = tailLink_;
|
|
tailLink_ = other.tailLink_;
|
|
other.tailLink_ = tempTailLink;
|
|
BucketIndex tempBucketsSize = bucketsSize_;
|
|
bucketsSize_ = other.bucketsSize_;
|
|
other.bucketsSize_ = tempBucketsSize;
|
|
BucketIndex tempItemCount = itemCount_;
|
|
itemCount_ = other.itemCount_;
|
|
other.itemCount_ = tempItemCount;
|
|
}
|
|
|
|
|
|
void
|
|
ValueInternalMap::clear ()
|
|
{
|
|
ValueInternalMap dummy;
|
|
swap ( dummy );
|
|
}
|
|
|
|
|
|
ValueInternalMap::BucketIndex
|
|
ValueInternalMap::size () const
|
|
{
|
|
return itemCount_;
|
|
}
|
|
|
|
bool
|
|
ValueInternalMap::reserveDelta ( BucketIndex growth )
|
|
{
|
|
return reserve ( itemCount_ + growth );
|
|
}
|
|
|
|
bool
|
|
ValueInternalMap::reserve ( BucketIndex newItemCount )
|
|
{
|
|
if ( !buckets_ && newItemCount > 0 )
|
|
{
|
|
buckets_ = mapAllocator ()->allocateMapBuckets ( 1 );
|
|
bucketsSize_ = 1;
|
|
tailLink_ = &buckets_[0];
|
|
}
|
|
|
|
// BucketIndex idealBucketCount = (newItemCount + ValueInternalLink::itemPerLink) / ValueInternalLink::itemPerLink;
|
|
return true;
|
|
}
|
|
|
|
|
|
const Value*
|
|
ValueInternalMap::find ( const char* key ) const
|
|
{
|
|
if ( !bucketsSize_ )
|
|
return 0;
|
|
|
|
HashKey hashedKey = hash ( key );
|
|
BucketIndex bucketIndex = hashedKey % bucketsSize_;
|
|
|
|
for ( const ValueInternalLink* current = &buckets_[bucketIndex];
|
|
current != 0;
|
|
current = current->next_ )
|
|
{
|
|
for ( BucketIndex index = 0; index < ValueInternalLink::itemPerLink; ++index )
|
|
{
|
|
if ( current->items_[index].isItemAvailable () )
|
|
return 0;
|
|
|
|
if ( strcmp ( key, current->keys_[index] ) == 0 )
|
|
return ¤t->items_[index];
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
Value*
|
|
ValueInternalMap::find ( const char* key )
|
|
{
|
|
const ValueInternalMap* constThis = this;
|
|
return const_cast<Value*> ( constThis->find ( key ) );
|
|
}
|
|
|
|
|
|
Value&
|
|
ValueInternalMap::resolveReference ( const char* key,
|
|
bool isStatic )
|
|
{
|
|
HashKey hashedKey = hash ( key );
|
|
|
|
if ( bucketsSize_ )
|
|
{
|
|
BucketIndex bucketIndex = hashedKey % bucketsSize_;
|
|
ValueInternalLink** previous = 0;
|
|
BucketIndex index;
|
|
|
|
for ( ValueInternalLink* current = &buckets_[bucketIndex];
|
|
current != 0;
|
|
previous = ¤t->next_, current = current->next_ )
|
|
{
|
|
for ( index = 0; index < ValueInternalLink::itemPerLink; ++index )
|
|
{
|
|
if ( current->items_[index].isItemAvailable () )
|
|
return setNewItem ( key, isStatic, current, index );
|
|
|
|
if ( strcmp ( key, current->keys_[index] ) == 0 )
|
|
return current->items_[index];
|
|
}
|
|
}
|
|
}
|
|
|
|
reserveDelta ( 1 );
|
|
return unsafeAdd ( key, isStatic, hashedKey );
|
|
}
|
|
|
|
|
|
void
|
|
ValueInternalMap::remove ( const char* key )
|
|
{
|
|
HashKey hashedKey = hash ( key );
|
|
|
|
if ( !bucketsSize_ )
|
|
return;
|
|
|
|
BucketIndex bucketIndex = hashedKey % bucketsSize_;
|
|
|
|
for ( ValueInternalLink* link = &buckets_[bucketIndex];
|
|
link != 0;
|
|
link = link->next_ )
|
|
{
|
|
BucketIndex index;
|
|
|
|
for ( index = 0; index < ValueInternalLink::itemPerLink; ++index )
|
|
{
|
|
if ( link->items_[index].isItemAvailable () )
|
|
return;
|
|
|
|
if ( strcmp ( key, link->keys_[index] ) == 0 )
|
|
{
|
|
doActualRemove ( link, index, bucketIndex );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ValueInternalMap::doActualRemove ( ValueInternalLink* link,
|
|
BucketIndex index,
|
|
BucketIndex bucketIndex )
|
|
{
|
|
// find last item of the bucket and swap it with the 'removed' one.
|
|
// set removed items flags to 'available'.
|
|
// if last page only contains 'available' items, then desallocate it (it's empty)
|
|
ValueInternalLink*& lastLink = getLastLinkInBucket ( index );
|
|
BucketIndex lastItemIndex = 1; // a link can never be empty, so start at 1
|
|
|
|
for ( ;
|
|
lastItemIndex < ValueInternalLink::itemPerLink;
|
|
++lastItemIndex ) // may be optimized with dicotomic search
|
|
{
|
|
if ( lastLink->items_[lastItemIndex].isItemAvailable () )
|
|
break;
|
|
}
|
|
|
|
BucketIndex lastUsedIndex = lastItemIndex - 1;
|
|
Value* valueToDelete = &link->items_[index];
|
|
Value* valueToPreserve = &lastLink->items_[lastUsedIndex];
|
|
|
|
if ( valueToDelete != valueToPreserve )
|
|
valueToDelete->swap ( *valueToPreserve );
|
|
|
|
if ( lastUsedIndex == 0 ) // page is now empty
|
|
{
|
|
// remove it from bucket linked list and delete it.
|
|
ValueInternalLink* linkPreviousToLast = lastLink->previous_;
|
|
|
|
if ( linkPreviousToLast != 0 ) // can not deleted bucket link.
|
|
{
|
|
mapAllocator ()->releaseMapLink ( lastLink );
|
|
linkPreviousToLast->next_ = 0;
|
|
lastLink = linkPreviousToLast;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Value dummy;
|
|
valueToPreserve->swap ( dummy ); // restore deleted to default Value.
|
|
valueToPreserve->setItemUsed ( false );
|
|
}
|
|
|
|
--itemCount_;
|
|
}
|
|
|
|
|
|
ValueInternalLink*&
|
|
ValueInternalMap::getLastLinkInBucket ( BucketIndex bucketIndex )
|
|
{
|
|
if ( bucketIndex == bucketsSize_ - 1 )
|
|
return tailLink_;
|
|
|
|
ValueInternalLink*& previous = buckets_[bucketIndex + 1].previous_;
|
|
|
|
if ( !previous )
|
|
previous = &buckets_[bucketIndex];
|
|
|
|
return previous;
|
|
}
|
|
|
|
|
|
Value&
|
|
ValueInternalMap::setNewItem ( const char* key,
|
|
bool isStatic,
|
|
ValueInternalLink* link,
|
|
BucketIndex index )
|
|
{
|
|
char* duplicatedKey = valueAllocator ()->makeMemberName ( key );
|
|
++itemCount_;
|
|
link->keys_[index] = duplicatedKey;
|
|
link->items_[index].setItemUsed ();
|
|
link->items_[index].setMemberNameIsStatic ( isStatic );
|
|
return link->items_[index]; // items already default constructed.
|
|
}
|
|
|
|
|
|
Value&
|
|
ValueInternalMap::unsafeAdd ( const char* key,
|
|
bool isStatic,
|
|
HashKey hashedKey )
|
|
{
|
|
JSON_ASSERT_MESSAGE ( bucketsSize_ > 0, "ValueInternalMap::unsafeAdd(): internal logic error." );
|
|
BucketIndex bucketIndex = hashedKey % bucketsSize_;
|
|
ValueInternalLink*& previousLink = getLastLinkInBucket ( bucketIndex );
|
|
ValueInternalLink* link = previousLink;
|
|
BucketIndex index;
|
|
|
|
for ( index = 0; index < ValueInternalLink::itemPerLink; ++index )
|
|
{
|
|
if ( link->items_[index].isItemAvailable () )
|
|
break;
|
|
}
|
|
|
|
if ( index == ValueInternalLink::itemPerLink ) // need to add a new page
|
|
{
|
|
ValueInternalLink* newLink = mapAllocator ()->allocateMapLink ();
|
|
index = 0;
|
|
link->next_ = newLink;
|
|
previousLink = newLink;
|
|
link = newLink;
|
|
}
|
|
|
|
return setNewItem ( key, isStatic, link, index );
|
|
}
|
|
|
|
|
|
ValueInternalMap::HashKey
|
|
ValueInternalMap::hash ( const char* key ) const
|
|
{
|
|
HashKey hash = 0;
|
|
|
|
while ( *key )
|
|
hash += *key++ * 37;
|
|
|
|
return hash;
|
|
}
|
|
|
|
|
|
int
|
|
ValueInternalMap::compare ( const ValueInternalMap& other ) const
|
|
{
|
|
int sizeDiff ( itemCount_ - other.itemCount_ );
|
|
|
|
if ( sizeDiff != 0 )
|
|
return sizeDiff;
|
|
|
|
// Strict order guaranty is required. Compare all keys FIRST, then compare values.
|
|
IteratorState it;
|
|
IteratorState itEnd;
|
|
makeBeginIterator ( it );
|
|
makeEndIterator ( itEnd );
|
|
|
|
for ( ; !equals (it, itEnd); increment (it) )
|
|
{
|
|
if ( !other.find ( key ( it ) ) )
|
|
return 1;
|
|
}
|
|
|
|
// All keys are equals, let's compare values
|
|
makeBeginIterator ( it );
|
|
|
|
for ( ; !equals (it, itEnd); increment (it) )
|
|
{
|
|
const Value* otherValue = other.find ( key ( it ) );
|
|
int valueDiff = value (it).compare ( *otherValue );
|
|
|
|
if ( valueDiff != 0 )
|
|
return valueDiff;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
ValueInternalMap::makeBeginIterator ( IteratorState& it ) const
|
|
{
|
|
it.map_ = const_cast<ValueInternalMap*> ( this );
|
|
it.bucketIndex_ = 0;
|
|
it.itemIndex_ = 0;
|
|
it.link_ = buckets_;
|
|
}
|
|
|
|
|
|
void
|
|
ValueInternalMap::makeEndIterator ( IteratorState& it ) const
|
|
{
|
|
it.map_ = const_cast<ValueInternalMap*> ( this );
|
|
it.bucketIndex_ = bucketsSize_;
|
|
it.itemIndex_ = 0;
|
|
it.link_ = 0;
|
|
}
|
|
|
|
|
|
bool
|
|
ValueInternalMap::equals ( const IteratorState& x, const IteratorState& other )
|
|
{
|
|
return x.map_ == other.map_
|
|
&& x.bucketIndex_ == other.bucketIndex_
|
|
&& x.link_ == other.link_
|
|
&& x.itemIndex_ == other.itemIndex_;
|
|
}
|
|
|
|
|
|
void
|
|
ValueInternalMap::incrementBucket ( IteratorState& iterator )
|
|
{
|
|
++iterator.bucketIndex_;
|
|
JSON_ASSERT_MESSAGE ( iterator.bucketIndex_ <= iterator.map_->bucketsSize_,
|
|
"ValueInternalMap::increment(): attempting to iterate beyond end." );
|
|
|
|
if ( iterator.bucketIndex_ == iterator.map_->bucketsSize_ )
|
|
iterator.link_ = 0;
|
|
else
|
|
iterator.link_ = & (iterator.map_->buckets_[iterator.bucketIndex_]);
|
|
|
|
iterator.itemIndex_ = 0;
|
|
}
|
|
|
|
|
|
void
|
|
ValueInternalMap::increment ( IteratorState& iterator )
|
|
{
|
|
JSON_ASSERT_MESSAGE ( iterator.map_, "Attempting to iterator using invalid iterator." );
|
|
++iterator.itemIndex_;
|
|
|
|
if ( iterator.itemIndex_ == ValueInternalLink::itemPerLink )
|
|
{
|
|
JSON_ASSERT_MESSAGE ( iterator.link_ != 0,
|
|
"ValueInternalMap::increment(): attempting to iterate beyond end." );
|
|
iterator.link_ = iterator.link_->next_;
|
|
|
|
if ( iterator.link_ == 0 )
|
|
incrementBucket ( iterator );
|
|
}
|
|
else if ( iterator.link_->items_[iterator.itemIndex_].isItemAvailable () )
|
|
{
|
|
incrementBucket ( iterator );
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ValueInternalMap::decrement ( IteratorState& iterator )
|
|
{
|
|
if ( iterator.itemIndex_ == 0 )
|
|
{
|
|
JSON_ASSERT_MESSAGE ( iterator.map_, "Attempting to iterate using invalid iterator." );
|
|
|
|
if ( iterator.link_ == &iterator.map_->buckets_[iterator.bucketIndex_] )
|
|
{
|
|
JSON_ASSERT_MESSAGE ( iterator.bucketIndex_ > 0, "Attempting to iterate beyond beginning." );
|
|
-- (iterator.bucketIndex_);
|
|
}
|
|
|
|
iterator.link_ = iterator.link_->previous_;
|
|
iterator.itemIndex_ = ValueInternalLink::itemPerLink - 1;
|
|
}
|
|
}
|
|
|
|
|
|
const char*
|
|
ValueInternalMap::key ( const IteratorState& iterator )
|
|
{
|
|
JSON_ASSERT_MESSAGE ( iterator.link_, "Attempting to iterate using invalid iterator." );
|
|
return iterator.link_->keys_[iterator.itemIndex_];
|
|
}
|
|
|
|
const char*
|
|
ValueInternalMap::key ( const IteratorState& iterator, bool& isStatic )
|
|
{
|
|
JSON_ASSERT_MESSAGE ( iterator.link_, "Attempting to iterate using invalid iterator." );
|
|
isStatic = iterator.link_->items_[iterator.itemIndex_].isMemberNameStatic ();
|
|
return iterator.link_->keys_[iterator.itemIndex_];
|
|
}
|
|
|
|
|
|
Value&
|
|
ValueInternalMap::value ( const IteratorState& iterator )
|
|
{
|
|
JSON_ASSERT_MESSAGE ( iterator.link_, "Attempting to iterate using invalid iterator." );
|
|
return iterator.link_->items_[iterator.itemIndex_];
|
|
}
|
|
|
|
|
|
int
|
|
ValueInternalMap::distance ( const IteratorState& x, const IteratorState& y )
|
|
{
|
|
int offset = 0;
|
|
IteratorState it = x;
|
|
|
|
while ( !equals ( it, y ) )
|
|
increment ( it );
|
|
|
|
return offset;
|
|
}
|