!=========================================================================
! Copyright (C) VMware, Inc. 1986-2011.  All Rights Reserved.
!
! $Id: rckeyvaluedict.gs,v 1.14 2008-01-09 22:50:14 stever Exp $
!
! Superclass Hierarchy:
!   RcKeyValueDictionary, AbstractDictionary, Collection, Object.
!
!=========================================================================

! class created in idxclasses.topaz

removeallmethods RcKeyValueDictionary
removeallclassmethods RcKeyValueDictionary

! ------------------- Class methods for RcKeyValueDictionary

category: 'For Documentation Installation only'
classmethod: RcKeyValueDictionary
installDocumentation

| doc txt |
doc := GsClassDocumentation newForClass: self.

txt := (GsDocText new) details:
'RcKeyValueDictionary is an AbstractDictionary that shares many of the protocols
 and characteristics of KeyValueDictionary.  Like all dictionaries, it stores
 key/value pairs.  In an RcKeyValueDictionary, keys may be of mixed classes.

 Like KeyValueDictionary, RcKeyValueDictionary stores key/value pairs under an
 index that is generated by applying a hash function to the key; it does not use
 Associations.  The hashing improves retrieval speed.  However, you must observe
 an important restriction: after a key/value pair has been added to an
 RcKeyValueDictionary, you must not modify the key.  Doing so renders the value
 inaccessible.

 An RcKeyValueDictionary is also an equality-based collection.  That is, two
 keys or two values are considered to be the same if they are equivalent; they
 need not be identical to be the same.  Thus, if you add two key-value pairs to
 an RcKeyValueDictionary but the keys are equivalent, even if they are not
 identical, then the result is that the second pair overwrites the first one,
 because the keys are the same.

 However, unlike KeyValueDictionary, RcKeyValueDictionary provides for
 concurrent handling of an individual instance by multiple sessions.  Any or all
 of those sessions can modify the single instance.  When that happens,
 RcKeyValueDictionary also reduces (but does not eliminate) the transaction
 conflicts that can arise among those sessions when they attempt to commit the
 instance to GemStone.

 Commit Conflicts.

 In general, RcKeyValueDictionaries do not cause concurrency conflicts for write
 operations that are commutative (operations that can be performed in any order
 without affecting the final GemStone state).  However, under some circumstances
 a user may experience conflict for commutative operations when the basicSize of
 the dictionary is too small (relative to the number of write operations
 performed in a transaction).  This can be avoided by creating a larger
 dictionary with the new: method, or increasing an existing dictionary''s size
 with the rebuildTable: method.

 If multiple users change values for different keys in a single
 RcKeyValueDictionary, the changes do not usually cause conflicts at commit
 time.  However, there is a (narrow and uncommon) window of time over which
 users have no control during which such a set of changes could result in
 conflicts.' .
doc documentClassWith: txt.

txt := (GsDocText new) details:
'A SmallInteger that represents the number of collisions allowed in a bucket
 before rebuilding the hash table.' .
doc documentInstVar: #collisionLimitPerBucket with: txt.

self description: doc.
%

category: 'Accessing the Class Format'
classmethod: RcKeyValueDictionary
firstPublicInstVar

"Returns the index of the first publicly available instance variable storage
 location, whether or not a public instance variable has actually been defined."

^ 3
%

category: 'Instance Creation'
classmethod: RcKeyValueDictionary
new

"Returns a RcKeyValueDictionary with the default table size."

^ self new: 53
%

category: 'Instance Creation'
classmethod: RcKeyValueDictionary
new: tableSize

"Returns a RcKeyValueDictionary with the specified table size."

^ super _basicNew initialize: (Integer _selectedPrimeGreaterThan: tableSize)

%

! ------------------- Instance methods for RcKeyValueDictionary
category: 'Comparing'
method: RcKeyValueDictionary
= aKeyValueDictionary

"Compares two RcKeyValueDictionaries for equality.  Returns true if all of
 the following conditions are true:

 1.  the receiver and aKeyValueDictionary are of the same class,
 2.  the two RcKeyValueDictionaries have the same number of elements
 3.  all of the keys in one dictionary return the same value in
     both dictionaries.

 Returns false otherwise."

| systm |
systm := System .
systm _addRootObjectToRcReadSet: self.
self == aKeyValueDictionary ifTrue:[ ^ true ].
( self class == aKeyValueDictionary class )
    ifFalse: [ ^ false ].

( self size == aKeyValueDictionary size )
    ifFalse: [ ^ false ].

systm _addEntireObjectToRcReadSet: self.
self keysDo: [ :aKey |
    ( (self at: aKey)  == (aKeyValueDictionary at: aKey ifAbsent: [ ^ false ]))
        ifFalse: [ ^ false ]
].
^ true
%

category: 'Updating'
method: RcKeyValueDictionary
_at: aKey put: aValue oldValue: oldVal logging: aBoolean

"Stores the aKey/aValue pair in the key-value dictionary.  Rebuilds the hash
 table if the addition caused the number of collisions to exceed the limit
 allowed.  The argument aBoolean determines if an entry is placed in the redo
 log for this operation."

| hash collisionBkt sz num oldValue |
aKey == nil ifTrue: [ ^ self _error: #rtErrNilKey ].
hash := self hashFunction: aKey.
collisionBkt := self _rcAt: hash.
num := collisionBkt numElements.
oldValue := collisionBkt at: aKey put: aValue.

aBoolean
    ifTrue: [
        self _logAt: aKey put: aValue oldValue: oldValue inCollisionBucket: collisionBkt.

        num < collisionBkt numElements
            ifTrue: [ | systm |
                systm := System .
                (sz := systm rcValueCacheAt: #size for: self otherwise: nil) ~~ nil
                    ifTrue: [ systm rcValueCacheAt: #size put: (sz + 1) for: self ]

            ].
    "if not logging, then doing replay. Don't rebuildTable during replay, since there is a 
     redoLog logEntry for rebuildTable"
	(collisionBkt size > collisionLimitPerBucket)
  		ifTrue: [
    			self rebuildTable: (Integer _selectedPrimeGreaterThan: self _basicSize * 2)
  		].
    ]
    ifFalse: [
      oldValue ~~ oldVal ifTrue: [ ^false ].
    ].

^ true
%

category: 'Accessing'
method: RcKeyValueDictionary
_calculateSize

"Calculates the number of key/value pairs in the receiver and places it in the
 RC value cache.  Returns the total."

| num collisionBkt systm |
num := 0.
systm := System .
systm _addEntireObjectToRcReadSet: self.
1 to: self _basicSize do: [ :i |
    collisionBkt := self basicAt: i.
    systm _addRootObjectToRcReadSet: collisionBkt.
    num := num + collisionBkt size
].
systm rcValueCacheAt: #size put: num for: self.
^ num
%

category: 'Enumerating'
method: RcKeyValueDictionary
_doCollisionBuckets: aBlock

"Evaluates aBlock with each of the receiver's collision buckets as the
 argument.  The argument aBlock must be a one-argument block."

| collisionBkt |
1 to: self _basicSize do: [ :tableIndex |
    collisionBkt := self basicAt: tableIndex.
    aBlock value: collisionBkt
]
%

category: 'Locking Support'
method: RcKeyValueDictionary
_lockableValues

"Returns an Array of the receiver's keys and values."

| result |
result := Array new .
self keysAndValuesDo: [:aKey :aValue |
  result add: aKey .
  result add: aValue .
] .
^ result
%

category: 'Private'
method: RcKeyValueDictionary
_logAt: aKey put: aValue oldValue: oldValue inCollisionBucket: aCollisionBkt

"Create a log entry for adding the given key and value.  The collision bucket
 is the object on which a conflict may occur."

| logEntry |
logEntry := LogEntry new.
logEntry receiver: self;
    selector: #_at:put:oldValue:logging: ;
    argArray: #[ aKey, aValue, oldValue, false ].

System redoLog addLogEntry: logEntry forConflictObject: aCollisionBkt
%

category: 'Private'
method: RcKeyValueDictionary
_logRemoveKey: aKey inCollisionBucket: aCollisionBkt

"Create a log entry for removing the given key.  The collision bucket is the
 object on which a conflict may occur."

| logEntry |
logEntry := LogEntry new.
logEntry receiver: self;
    selector: #_removeKey:logging:;
    argArray: #[ aKey, false ].
System redoLog addLogEntry: logEntry forConflictObject: aCollisionBkt
%

! deleted _bucket:replayRemoveKey:

! deleted _bucket:replayAt:put:oldValue:

category: 'Private'
method: RcKeyValueDictionary
_resolveRcConflictsWith: conflictObjects

"A logical write-write conflict has occurred on the receiver.  The objects that
 had the actual physical write-write conflicts are in the conflictObjects
 Array.  Selectively abort the receiver and then attempt to replay the
 operations maintained in the System redo log.  Returns true if all the
 operations could be replayed; otherwise returns false."

| redoLog logEntries |

redoLog := System _redoLog.
    
" if no log entries to replay, then we're done "
redoLog == nil ifTrue: [ ^ false ].
logEntries := redoLog getLogEntriesFor: self .
logEntries == nil ifTrue:[ ^ false ].
   
" cannot perform selective abort if receiver has a dependency tag "
self _hasDependencyList ifTrue: [ ^ false ].

" fix 33731, explicitly buckets that have conflicts in case 
 a rebuild has happened and the old bucket is no longer referenced from 
 the parent.  This abort prevents committing changes to the old bucket 
 after a previous transaction dereferenced it from the dictionary.
"
1 to: conflictObjects size do:[ :j |
  (conflictObjects at: j) _selectiveAbort 
].
    
"abort all of the nodes involved in the transaction"
redoLog conflictObjects keysAndValuesDo:[ :conflictObject :redoObject |
  redoObject == self ifTrue: [ conflictObject _selectiveAbort ].
].

" refresh the state of the receiver "
self _selectiveAbort.
    
" replay all operations on the receiver"
1 to: logEntries size do:[:j | | logEnt args |
  logEnt := logEntries at: j .
  logEnt redo ifFalse:[ 
    ^ false  "redo failed, cannot commit"
  ].
].
^ true
%

! _selectiveAbort now inherited from Object  , with fix 33731
! addAll: now inherited from AbstractDictionary with fix 36675

category: 'Enumerating'
method: RcKeyValueDictionary
associationsDo: aBlock

"(R) Iteratively evaluates the one argument block, aBlock, using an Association
 created by using each key-value pair successively.  Returns the receiver."

self keysAndValuesDo: [:aKey :aValue |
   aBlock value: (Association newWithKey: aKey value: aValue)
].
%

category: 'Accessing'
method: RcKeyValueDictionary
at: aKey ifAbsent: aBlock

"Returns the value that corresponds to aKey.  If no such key/value pair exists,
 returns the result of evaluating the zero-argument block aBlock."

| hash |
aKey == nil ifTrue: [ ^ self _error: #rtErrNilKey ].

hash := self hashFunction: aKey.
^ (self _rcAt: hash) at: aKey ifAbsent: aBlock
%

category: 'Accessing'
method: RcKeyValueDictionary
at: aKey otherwise: aValue

"Returns the value that corresponds to aKey.  If no such key/value pair exists,
 returns the given alternate value."

| hash |
aKey == nil ifTrue: [ ^ self _error: #rtErrNilKey ].

hash := self hashFunction: aKey.
^ (self _rcAt: hash) at: aKey otherwise: aValue
%

category: 'Updating'
method: RcKeyValueDictionary
at: aKey put: aValue

"(R) Adds the key-value pair to the receiver. If the receiver already contains 
 a key-value pair with the given key, this method makes aValue the value of that
 Association or key-value pair.  Returns aValue."

self _at: aKey put: aValue oldValue: nil logging: true.
^ aValue
%

category: 'Private'
method: RcKeyValueDictionary
atHash: hashIndex put: aCollisionBucket

"Updates the hash table by storing aKey at the specified hashIndex.  This
 method is intended to be used internally.  If it is invoked directly by an
 application, concurrency conflicts may result (as well as destroying the
 consistency of the dictionary)."

((hashIndex <= 0) | (hashIndex > self _basicSize))
  ifTrue: [ hashIndex _error: #rtErrArgOutOfRange  .
            self _uncontinuableError
          ].
self basicAt: hashIndex put: aCollisionBucket
%

category: 'Clustering'
method: RcKeyValueDictionary
clusterDepthFirst

"This method clusters the receiver and its named instance variables and the
 key/value pairs in depth-first order.  Returns true if the receiver has
 already been clustered during the current transaction; returns false
 otherwise."

System _addEntireObjectToRcReadSet: self.
self cluster
  ifTrue: [ ^ true ]
  ifFalse: [
    " cluster collision buckets "
    1 to: self _basicSize do: [ :i | (self basicAt: i) cluster ].

    " cluster instance variables "
    1 to: self class instSize do: [ :i | (self instVarAt: i) clusterDepthFirst ].

    " cluster keys and values "
    self keysAndValuesDo: [ :key :value |
      key clusterDepthFirst.
      value clusterDepthFirst
    ].
    ^ false
  ].
%

category: 'Copying'
method: RcKeyValueDictionary
copy

"Copies the collision buckets and returns a copy of the receiver."

| newKeyValueDict collisionBkt systm |
systm := System .
systm _addEntireObjectToRcReadSet: self.
newKeyValueDict := super copy.
1 to: self _basicSize do: [ :index |
    collisionBkt := self basicAt: index.
    systm _addEntireObjectToRcReadSet: collisionBkt.
    newKeyValueDict atHash: index put: collisionBkt copy
].
^ newKeyValueDict
%

category: 'Enumerating'
method: RcKeyValueDictionary
valuesDo: aBlock

"(R) Iteratively evaluates the one argument block aBlock, using the value
 of each Association as the argument of the block."

self do: aBlock
%

category: 'Enumerating'
method: RcKeyValueDictionary
do: aBlock

"(R) Iteratively evaluates the one argument block aBlock, using the value
 of each Association as the argument of the block."
 
self keysAndValuesDo: [ :key :value | aBlock value: value ].
%

category: 'Comparing'
method: RcKeyValueDictionary
hash

"Since RcKeyValueDictionary | = is based on identity of elements, hash is based
 on identityHash of elements.  Returns the numerical hash value."

| hashValue |
System _addEntireObjectToRcReadSet: self.
hashValue := 97633.
self keysDo: [ :aKey |
    hashValue := hashValue bitXor: aKey hash
].
^ hashValue
%

category: 'Private'
method: RcKeyValueDictionary
tableSize

"(R) Returns the size of hash table used for storing the entries."

^ self _basicSize
%

category: 'Hashing'
method: RcKeyValueDictionary
hashFunction: aKey

"(R) The hash function should perform some operation on the value of the key 
 aKey which returns a value in the range 1..self _basicSize."

^ (aKey hash \\ self _basicSize) + 1
%

category: 'Initializing'
method: RcKeyValueDictionary
initialize: itsSize

"Initializes the instance variables of the receiver to be an empty
 RcKeyValueDictionary of the specified size.  This is intended to be used
 internally at the time of instance creation.  If used on an existing,
 populated RcKeyValueDictionary, concurrency conflicts may result."

"Returns the receiver."

| constraintClass sz theSize bucket systm |
theSize := itsSize .
(theSize <= 0)
    ifTrue: [ theSize _error: #rtErrArgNotPositive  .
              theSize := 1
    ].
collisionLimitPerBucket := theSize // 4.
constraintClass := self class varyingConstraint.
self _basicSize: theSize.
1 to: theSize do: [ :i |
    bucket := constraintClass new.
    bucket assignToSegment: self segment.
    bucket keyValueDictionary: self.
    self basicAt: i put: bucket.
].

systm := System .
sz := systm rcValueCacheAt: #size for: self otherwise: nil.
sz ~~ nil
    ifTrue: [ systm rcValueCacheAt: #size put: 0 for: self ]
%

category: 'Accessing'
method: RcKeyValueDictionary
keyAtValue: anObject ifAbsent: aBlock

"(R) Returns the key of the first value equal to the given object, anObject.
 If no match is found, evaluates and returns the result of the block aBlock."

"Note: In GemStone 4.1, this method checked for a value identical to anObject
 as opposed to checking for equality."

self keysAndValuesDo: [ :aKey :aValue |
  (anObject = aValue) ifTrue: [ ^ aKey ].
  ].
^ aBlock value.
%

category: 'Accessing'
method: RcKeyValueDictionary
keys

"Returns an IdentitySet containing the receiver's keys."

| keys |
keys := IdentitySet new.
self keysDo: [ :aKey | keys add: aKey ].
^ keys
%

category: 'Enumerating'
method: RcKeyValueDictionary
keysAndValuesDo: aBlock

"Iteratively evaluates the two argument block, aBlock, using each key and value
 of the receiver as the argument to the block. Returns the receiver."

System _addEntireObjectToRcReadSet: self.
self _doCollisionBuckets: [ :collisionBkt |
    1 to: collisionBkt tableSize do: [ :j | | aKey |
        aKey := collisionBkt keyAt: j.
        nil == aKey
            ifFalse: [ | aValue |
                aValue := collisionBkt valueAt: j.
                aBlock value: aKey value: aValue.
            ]
    ]
].

%

! added for 36675
category: 'Enumerating'
method: RcKeyValueDictionary
inject: anObj keysAndValuesInto: aBlock

"Evaluates aBlock with each of the receiver's key/value pairs as the
 2nd and 3rd arguments.
 aBlock must be a 3 argument block, with arguments anObj, key value ."

System _addEntireObjectToRcReadSet: self.
self _doCollisionBuckets: [ :collisionBkt |
    1 to: collisionBkt tableSize do: [ :j | | aKey |
        aKey := collisionBkt keyAt: j.
        nil == aKey
            ifFalse: [ | aValue |
                aValue := collisionBkt valueAt: j.
                aBlock value: anObj value: aKey value: aValue.
            ]
    ]
].
%


category: 'Enumerating'
method: RcKeyValueDictionary
keysDo: aBlock

"(R) Iteratively evaluates the two argument block aBlock, using each key and 
 value of the receiver as the argument to the block."

System _addEntireObjectToRcReadSet: self.
self _doCollisionBuckets: [ :collisionBkt | collisionBkt keysDo: aBlock ]
%

category: 'Accessing'
method: RcKeyValueDictionary
numElements

"Same as size."

^ self size
%

category: 'Hashing'
method: RcKeyValueDictionary
_rebuildTable: newSize logging: aBoolean

"Rebuilds the hash table by saving the current state, initializing and changing
 the size of the table, and adding the key value pairs saved back to the hash
 dictionary.  This method is intended to be used internally.  If it is invoked
 directly by an application, concurrency conflicts may result."

| origSize constraintClass saveArray thisKey collBkt mySeg redoLog |
(newSize <= 0) ifTrue: [ 
  newSize _error: #rtErrArgNotPositive  .  
  ^ self 
].

origSize := self _basicSize.
mySeg := self segment .

constraintClass := self class varyingConstraint.
saveArray := Array new: origSize.

" save all old buckets in an Array and create new ones."
1 to: origSize do: [ :i | | oldBkt |
  oldBkt := self _at: i .
  saveArray at: i put: oldBkt .

  collBkt := constraintClass new.
  collBkt assignToSegment: mySeg.
  collBkt keyValueDictionary: self.
  self _at: i put: collBkt.
].

super _basicSize: newSize.

newSize > origSize
  ifTrue: [ " growing the RcKeyValueDictionary "
    origSize + 1 to: newSize do: [ :i | 
      collBkt := constraintClass new.
      collBkt assignToSegment: mySeg.
      collBkt keyValueDictionary: self.
      self _at: i put: collBkt.
    ]
  ].

collisionLimitPerBucket := newSize // 4.

redoLog := System redoLog.
1 to: origSize do: [ :i |
  collBkt := saveArray _at: i.
  aBoolean ifTrue: [ redoLog addConflictObject: collBkt for: self ].
  1 to: collBkt _basicSize by: 2 do: [ :j |
    thisKey := collBkt _rcAt: j. "make sure collBkt is in rc read set"
      nil == thisKey
        ifFalse: [ self _at: thisKey put: (collBkt _at: j+1) oldValue: nil logging: false ].
    ].
  collBkt _removeAll .  "dereference parent from old bucket"
  ].
saveArray size: 0.

aBoolean
	ifTrue: [ | logEntry |
		logEntry := LogEntry new.
		logEntry receiver: self;
    			selector: #_rebuildTable:logging: ;
    			argArray: #[ newSize, false ].
		redoLog addLogEntry: logEntry forConflictObject: self.
	].

^true
%

category: 'Hashing'
method: RcKeyValueDictionary
rebuildTable: newSize

"Rebuilds the hash table by saving the current state, initializing and changing
 the size of the table, and adding the key value pairs saved back to the hash
 dictionary.  This method is intended to be used internally.  If it is invoked
 directly by an application, concurrency conflicts may result."

self _rebuildTable: newSize logging: true
%

category: 'Removing'
method: RcKeyValueDictionary
_removeKey: aKey logging: aBoolean

"(R) Removes the key/value pair with key aKey from the receiver and returns the
 value.  If no key/value pair is present with key aKey, evaluates the
 zero-argument block aBlock and returns the result of that evaluation."

| hash collisionBkt aValue sz systm |
hash := self hashFunction: aKey.
collisionBkt := self _rcAt: hash.

aValue := collisionBkt
    removeKey: aKey
    ifAbsent: [ ^ false ].

aBoolean 
	ifTrue: [
		" log the removal in the redo log "
		self _logRemoveKey: aKey inCollisionBucket: collisionBkt.

		systm := System .
		(sz := systm rcValueCacheAt: #size for: self otherwise: nil) == nil
    			ifTrue: [ ^ aValue ].
		systm rcValueCacheAt: #size put: (sz - 1) for: self.
	].

^ true
%


category: 'Removing'
method: RcKeyValueDictionary
removeKey: aKey ifAbsent: aBlock

"(R) Removes the key/value pair with key aKey from the receiver and returns the
 value.  If no key/value pair is present with key aKey, evaluates the
 zero-argument block aBlock and returns the result of that evaluation."

| hash collisionBkt aValue sz systm |
hash := self hashFunction: aKey.
collisionBkt := self _rcAt: hash.

aValue := collisionBkt
    removeKey: aKey
    ifAbsent: [ ^ aBlock value ].

" log the removal in the redo log "
self _logRemoveKey: aKey inCollisionBucket: collisionBkt.

systm := System .
(sz := systm rcValueCacheAt: #size for: self otherwise: nil) == nil
    ifTrue: [ ^ aValue ].
systm rcValueCacheAt: #size put: (sz - 1) for: self.

^ aValue
%

category: 'Accessing'
method: RcKeyValueDictionary
size

"Returns the number of key/value pairs in the receiver."

| sz |
sz := System rcValueCacheAt: #size for: self otherwise: nil.
sz == nil
    ifTrue: [ sz := self _calculateSize ].
^ sz
%

category: 'Statistics'
method: RcKeyValueDictionary
statistics

"Returns a Dictionary containing statistics that can be useful in determining
 the performance of a key-value dictionary, including the following information:

 * TotalCollisionBuckets: The number of collision buckets required to
                          implement the key-value dictionary.

 * AvgPairsPerBucket:     The average number of key/value pairs in each bucket.

 * LargestBucket:         The bucket having the most key/value pairs.  This
                          bucket contains the most keys for which the hash
                          function did not provide a good distribution over the
                          range of values in the table.

 Since RcKeyValueDictionaries are implemented differently than
 KeyValueDictionaries, the statistics are calculated as if the implementation
 were the same as KeyValueDictionaries.  To be specific, RcKeyValueDictionaries
 are implemented with all collision buckets.  Therefore, the calculations don't
 count CollisionBuckets with a size of 1 as a collision."

| bktCount pairCount maxBkt maxBktSize result |
bktCount := 0.
pairCount :=0.
maxBktSize := 0.
System _addEntireObjectToRcReadSet: self.
self _doCollisionBuckets: [ :collisionBkt |
    System _addRootObjectToRcReadSet: collisionBkt.
    " if bucket size = 1, this would not be a collision in an ordinary key-value dictionary "
    collisionBkt size > 1
        ifTrue: [
            bktCount := bktCount + 1.
            pairCount := pairCount + collisionBkt size - 1.
            (collisionBkt size > maxBktSize)
                ifTrue: [
                    maxBktSize := collisionBkt size.
                    maxBkt := collisionBkt
                ]
        ]
].

result := SymbolDictionary new.
result at: #TotalCollisionBuckets put: bktCount.
(bktCount > 0)
    ifTrue: [ result at: #AvgPairsPerBucket put: (pairCount / bktCount) asFloat ]
    ifFalse: [ result at: #AvgPairsPerBucket put: 0].
result at: #LargestBucket put: maxBkt.
^ result
%

category: 'Initializing'
method: RcKeyValueDictionary
tableSize: aSize

"Sets the size of the receiver to a new value.  Each collision bucket is reset
 to contain no elements.  This method is intended to be used internally.  If it
 is invoked directly by an application, concurrency conflicts may result."

| origSize constraintClass collBkt mySeg |
(aSize <= 0) ifTrue: [ 
  aSize _error: #rtErrArgNotPositive  .  
  ^ self 
].

origSize := self _basicSize.
mySeg := self segment .
" reset each collision bucket to be empty "
1 to: origSize do: [ :i | (self basicAt: i) reset ].

super _basicSize: aSize.

aSize > origSize
    ifTrue: [ " growing the RcKeyValueDictionary "
        constraintClass := self class varyingConstraint.
        (origSize + 1) to: aSize do: [ :i |
            collBkt := constraintClass new.
            collBkt assignToSegment: mySeg.
            collBkt keyValueDictionary: self.
            self basicAt: i put: collBkt.
        ].
    ].

collisionLimitPerBucket := aSize // 4.
%

category: 'Accessing'
method: RcKeyValueDictionary
values

"Returns an OrderedCollection containing the receiver's values."

| values |
values := OrderedCollection new.
System _addEntireObjectToRcReadSet: self.
self valuesDo: [ :aValue | values add: aValue ].
^ values
%

category: 'Updating'
method: RcKeyValueDictionary
_collisionLimitPerBucket: aLimit

"Sets the collisionLimitPerBucket for the receiver."

collisionLimitPerBucket := aLimit
%

category: 'Repository Conversion'
method: RcKeyValueDictionary
rehashForConversion

"Private. Rehashes the receiver because the hash values of some of its keys
 may have changed."

| newSize |

newSize := (Integer _selectedPrimeGreaterThan: (self size)) max: (self _basicSize).
self rebuildTable: newSize .
^ self.
%
category: 'Storing and Loading'
method: RcKeyValueDictionary
loadVaryingFrom: passiveObj size: varyingSize

"Reads the varying part of the receiver from the given passive object.
 Does not record the receiver as having been read.  Does not read the
 receiver's named instance variables, if any."

| oldBkt newBkt |

passiveObj version >= 500 ifTrue:[
  1 to: varyingSize do: [:i |
    self at: passiveObj readObject"aKey" put: passiveObj readObject"aValue"
    ].
  ]
ifFalse:[
  1 to: varyingSize do: [:i |
    oldBkt := passiveObj readObject .
    (oldBkt isKindOf: RcCollisionBucket) 
       ifTrue:[ newBkt:= oldBkt ]
       ifFalse:[ 
         (oldBkt isKindOf: ObsoleteRcCollisionBucket) ifFalse:[
            self _halt:'invalid kind of RcCollisionBucket'.
            ].
         newBkt := RcCollisionBucket new .
         newBkt keyValueDictionary: self .
         oldBkt keysAndValuesDo:[:aKey :aValue | newBkt at: aKey put: aValue ] .
         oldBkt _removeAll .
         ].
    self _basicAt: i put: newBkt 
    ].
  ].
%

category: 'Storing and Loading'
classmethod: RcKeyValueDictionary
loadFrom: passiveObj 

"Reads from passiveObj the passive form of an object with named instance
 variable format.  Converts the object to its active form by loading the
 information into a new instance of the receiver.  Returns the new instance."

| newDict oldDict size |
(passiveObj version >= 500) ifTrue:[ 
  size := passiveObj readSize.
  newDict := self new.
  newDict loadFrom: passiveObj size: size.
  ]
ifFalse:[
  "handle activation of objects written by 4.1.3"

  newDict := self new .
  passiveObj hasRead: newDict .
  size := passiveObj readSize. 
  oldDict := self new .
  oldDict basicLoadFromNoRead: passiveObj size: size .
  oldDict keysAndValuesDo:[:aKey :aValue | newDict at: aKey put: aValue ].
  oldDict initialize: 1 .
].
^ newDict
%
category: 'Storing and Loading'
method: RcKeyValueDictionary
basicWriteTo: passiveObj

"Converts the receiver to its passive form and writes that information on
 passiveObj."

| s cls ivs c |
  cls := self class.
  passiveObj writeClass: cls.

  ivs := cls _instVarNames .

  passiveObj writeSize: (s := self size) .

  cls firstPublicInstVar to: cls instSize do: [:i |
    (self shouldWriteInstVar: (ivs at: i)) ifTrue: [
      passiveObj writeObject: (self instVarAt: i) named: (ivs at: i)
    ].
  ].

  passiveObj endNamedInstVars.

  c := 0.
  self keysAndValuesDo:[ :aKey :aValue | 
    passiveObj writeObject: aKey; writeObject: aValue .
    c := c + 1.
    c > 49 ifTrue: [
      passiveObj lf.
      c := 0.
      ].
    ].

  passiveObj cr
%

category: 'Private'
method: RcKeyValueDictionary
_validateRcConflictsWith: conflictObjects

"If the only conflict on this object is for the root, then we return true.
 Otherwise return false (to fail the transaction) if the redo log contains
 a removeKey: operation since we cannot guarantee success if the transaction
 did a removal from the RcKeyValueDictionary."

" if only had a physical conflict on the root "
( conflictObjects size = 1 _and: [ (conflictObjects at: 1) == self ] )
  ifTrue: [ ^ true ].

(self _checkLogEntriesForNoSelector: #_at:put:oldValue:logging: )
  ifFalse: [ ^ false ].

^ self _checkLogEntriesForNoSelector: #_removeKey:logging: 
%

category: 'Private'
method: RcKeyValueDictionary
_deferredGciUpdateWith: valueArray

"Private."

1 to: valueArray size by: 2 do:[:j |
  self at: (valueArray at: j) put: (valueArray at: j + 1)
  ].
%

category: 'Private'
method: RcKeyValueDictionary
_gciInitialize

"Private."

| cache |

self _basicSize == 0
  ifTrue: [ self initialize: 53 ].
1 to: self _basicSize do: [ :i | (self basicAt: i) reset ].

" remove receiver from rc value cache "
( (cache := System _rcValueCache) ~~ nil _and:
[ (cache at: self otherwise: nil) ~~ nil ] )
  ifTrue: [ cache removeKey: self ].
%

category: 'Updating'
method: RcKeyValueDictionary
changeToSegment: segment

"Assigns the receiver and any collision buckets to the given segment."

self assignToSegment: segment.
self _doCollisionBuckets: [ :cb | cb assignToSegment: segment ]

%

