You've already forked lazarus-ccr
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@6159 8e941d3f-bd1b-0410-a28a-d453659cc2b4
996 lines
24 KiB
ObjectPascal
996 lines
24 KiB
ObjectPascal
// Upgraded to Delphi 2009: Sebastian Zierer
|
|
|
|
(* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is TurboPower SysTools
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* TurboPower Software
|
|
*
|
|
* Portions created by the Initial Developer are Copyright (C) 1996-2002
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* ***** END LICENSE BLOCK ***** *)
|
|
|
|
{*********************************************************}
|
|
{* SysTools: StHASH.PAS 4.04 *}
|
|
{*********************************************************}
|
|
{* SysTools: Hash table class *}
|
|
{*********************************************************}
|
|
|
|
{$IFDEF FPC}
|
|
{$mode DELPHI}
|
|
{$ENDIF}
|
|
|
|
//{$I StDefine.inc}
|
|
|
|
{Notes:
|
|
- Generally the same as STDICT.PAS, but the hash table is
|
|
keyed on elements of arbitrary type rather than just strings.
|
|
|
|
- Also manages an LRU counter and updates each node's LRU when
|
|
it is added or accessed. If the maximum allowed number of nodes
|
|
in the table is exceeded, the least recently used node is
|
|
automatically removed from the table. By default, MaxLongInt
|
|
nodes can be in the table so the automatic removal logic does
|
|
not come into play. When a node is automatically removed, the
|
|
NodeRemoved virtual method is called to notify the program
|
|
that the node is being removed.
|
|
}
|
|
|
|
unit StHASH;
|
|
|
|
interface
|
|
|
|
uses
|
|
SysUtils,
|
|
Classes,
|
|
{$IFNDEF FPC}
|
|
{$IFDEF ThreadSafe}
|
|
Windows,
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
StConst,
|
|
StBase;
|
|
|
|
type
|
|
TStHashNode = class(TStNode)
|
|
{.Z+}
|
|
protected
|
|
hnNext : TStHashNode; {Next node in hash list}
|
|
hnValue: Pointer; {Pointer to value of element}
|
|
hnValSize : Cardinal; {Size of hnValue memory block}
|
|
FLRU : LongInt; {LRU counter of this node}
|
|
|
|
function GetValue : Pointer;
|
|
|
|
{.Z-}
|
|
public
|
|
constructor CreateNode(const AValue; AValSize : Cardinal; AData : Pointer);
|
|
virtual;
|
|
{-Initialize node}
|
|
destructor Destroy; override;
|
|
{-Free name string and destroy node}
|
|
|
|
property Value : Pointer
|
|
read GetValue;
|
|
property LRU : LongInt
|
|
read FLRU
|
|
write FLRU;
|
|
end;
|
|
|
|
{.Z+}
|
|
THashArray = array[0..(MaxInt div SizeOf(TStHashNode))-1] of TStHashNode;
|
|
PHashArray = ^THashArray;
|
|
{.Z-}
|
|
|
|
THashFunc = function (const V; Size : Integer) : Integer;
|
|
|
|
TStHashTable = class(TStContainer)
|
|
{.Z+}
|
|
protected
|
|
{property instance variables}
|
|
FValSize : Cardinal; {Size of each element in table}
|
|
FHashSize : Integer; {Bins in hash array}
|
|
FEqual : TUntypedCompareFunc; {Element compare function}
|
|
FHash : THashFunc; {Hash function}
|
|
FMaxNodes : LongInt; {Max nodes allowed in table}
|
|
|
|
{private instance variables}
|
|
htHeads : PHashArray; {Pointer to head of node lists}
|
|
htTails : PHashArray; {Pointer to tail of node lists}
|
|
htLRU : LongInt; {LRU counter}
|
|
htIgnoreDups : Boolean; {Ignore duplicates during Join?}
|
|
|
|
{protected undocumented methods}
|
|
procedure htInsertNode(H : Integer; This : TStHashNode);
|
|
procedure htIterate(Action : TIterateFunc; OtherData : Pointer;
|
|
var H : Integer; var Prev, This : TStHashNode);
|
|
procedure htSetEqual(E : TUntypedCompareFunc);
|
|
procedure htSetHash(H : THashFunc);
|
|
procedure htSetHashSize(Size : Integer);
|
|
procedure htSetMaxNodes(Nodes : LongInt);
|
|
procedure htMoveToFront(H : Integer; Prev, This : TStHashNode);
|
|
procedure htFindNode(const V; var H : Integer;
|
|
var Prev, This : TStHashNode);
|
|
procedure htUpdateLRU(This : TStHashNode);
|
|
procedure htDeleteOldestNode;
|
|
|
|
{.Z-}
|
|
public
|
|
constructor Create(AValSize : Cardinal; AHashSize : Integer); virtual;
|
|
{-Initialize an empty hash table}
|
|
destructor Destroy; override;
|
|
{-Destroy a hash table}
|
|
|
|
procedure LoadFromStream(S : TStream); override;
|
|
{-Read a hash table and its data from a stream}
|
|
procedure StoreToStream(S : TStream); override;
|
|
{-Write a hash table and its data to a stream}
|
|
|
|
procedure Clear; override;
|
|
{-Remove all nodes from container but leave it instantiated}
|
|
|
|
function Exists(const V; var Data : Pointer) : Boolean;
|
|
{-Return True and the Data pointer if V is in the hash table}
|
|
procedure Add(const V; Data : Pointer);
|
|
{-Add new value and Data to the hash table}
|
|
procedure Delete(const V);
|
|
{-Delete a value from the hash table}
|
|
procedure Update(const V; Data : Pointer);
|
|
{-Update the data for an existing element}
|
|
function Find(Data : Pointer; var V) : Boolean;
|
|
{-Return True and the element value that matches Data}
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
{-Assign another hash table's contents to this one}
|
|
procedure Join(H : TStHashTable; IgnoreDups : Boolean);
|
|
{-Add hash table H into this one and dispose H}
|
|
|
|
function Iterate(Action : TIterateFunc;
|
|
OtherData : Pointer) : TStHashNode;
|
|
{-Call Action for all the nodes, returning the last node visited}
|
|
|
|
procedure NodeRemoved(const V; Data : Pointer); virtual;
|
|
{-Called when a not recently used node is removed from the table}
|
|
|
|
function BinCount(H : Integer) : LongInt;
|
|
{-Return number of names in a hash bin (for testing)}
|
|
|
|
property Equal : TUntypedCompareFunc
|
|
{-Change the string compare function; only for an empty table}
|
|
read FEqual
|
|
write htSetEqual;
|
|
|
|
property Hash : THashFunc
|
|
{-Change the hash function; only for an empty table}
|
|
read FHash
|
|
write htSetHash;
|
|
|
|
property HashSize : Integer
|
|
{-Change the hash table size; preserves existing elements}
|
|
read FHashSize
|
|
write htSetHashSize;
|
|
|
|
property ValSize : Cardinal
|
|
{-Read the size of each element in the table}
|
|
read FValSize;
|
|
|
|
property MaxNodes : LongInt
|
|
{-Change the maximum nodes in the table}
|
|
read FMaxNodes
|
|
write htSetMaxNodes;
|
|
end;
|
|
|
|
{======================================================================}
|
|
|
|
implementation
|
|
|
|
{$IFDEF ThreadSafe}
|
|
var
|
|
ClassCritSect : TRTLCriticalSection;
|
|
{$ENDIF}
|
|
|
|
procedure EnterClassCS;
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCriticalSection(ClassCritSect);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure LeaveClassCS;
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
LeaveCriticalSection(ClassCritSect);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
|
|
|
|
{----------------------------------------------------------------------}
|
|
|
|
constructor TStHashNode.CreateNode(const AValue; AValSize : Cardinal;
|
|
AData : Pointer);
|
|
begin
|
|
Create(AData);
|
|
hnValSize := AValSize;
|
|
GetMem(hnValue, AValSize);
|
|
Move(AValue, hnValue^, AValSize);
|
|
end;
|
|
|
|
destructor TStHashNode.Destroy;
|
|
begin
|
|
if Assigned(hnValue) then
|
|
FreeMem(hnValue, hnValSize);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TStHashNode.GetValue : Pointer;
|
|
begin
|
|
Result := hnValue;
|
|
end;
|
|
|
|
{----------------------------------------------------------------------}
|
|
|
|
procedure TStHashTable.Add(const V; Data : Pointer);
|
|
var
|
|
H : Integer;
|
|
P, T : TStHashNode;
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
htFindNode(V, H, P, T);
|
|
if Assigned(T) then
|
|
RaiseContainerError(stscDupNode);
|
|
htInsertNode(H, TStHashNode.CreateNode(V, FValSize, Data));
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
LeaveCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function AssignNode(Container : TStContainer;
|
|
Node : TStNode;
|
|
OtherData : Pointer) : Boolean; far;
|
|
var
|
|
HashNode : TStHashNode absolute Node;
|
|
OurHashTbl : TStHashTable absolute OtherData;
|
|
begin
|
|
OurHashTbl.Add(HashNode.Value^, HashNode.Data);
|
|
Result := true;
|
|
end;
|
|
|
|
procedure TStHashTable.Assign(Source: TPersistent);
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
{The only container that we allow to be assigned to a hash table
|
|
is... another hash table}
|
|
if (Source is TStHashTable) then begin
|
|
Clear;
|
|
FValSize := TStHashTable(Source).ValSize;
|
|
TStHashTable(Source).Iterate(AssignNode, Self);
|
|
end
|
|
else
|
|
inherited Assign(Source);
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
LeaveCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
|
|
function TStHashTable.BinCount(H : Integer) : LongInt;
|
|
var
|
|
C : LongInt;
|
|
T : TStHashNode;
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
C := 0;
|
|
T := htHeads^[H];
|
|
while Assigned(T) do begin
|
|
inc(C);
|
|
T := T.hnNext;
|
|
end;
|
|
Result := C;
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
LeaveCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TStHashTable.Clear;
|
|
var
|
|
TableSize : Cardinal;
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
if FCount <> 0 then begin
|
|
Iterate(DestroyNode, nil);
|
|
FCount := 0;
|
|
htLRU := 0;
|
|
TableSize := FHashSize*SizeOf(TStHashNode);
|
|
FillChar(htHeads^, TableSize, 0);
|
|
FillChar(htTails^, TableSize, 0);
|
|
end;
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
LeaveCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
constructor TStHashTable.Create(AValSize : Cardinal; AHashSize : Integer);
|
|
begin
|
|
if AValSize = 0 then
|
|
RaiseContainerError(stscBadSize);
|
|
|
|
CreateContainer(TStHashNode, 0);
|
|
|
|
FValSize := AValSize;
|
|
FMaxNodes := MaxLongInt;
|
|
|
|
{allocate hash table by assigning to the HashSize property}
|
|
HashSize := AHashSize;
|
|
end;
|
|
|
|
procedure TStHashTable.Delete(const V);
|
|
var
|
|
H : Integer;
|
|
P, T : TStHashNode;
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
htFindNode(V, H, P, T);
|
|
if Assigned(T) then begin
|
|
if Assigned(P) then
|
|
P.hnNext := T.hnNext
|
|
else
|
|
htHeads^[H] := T.hnNext;
|
|
if T = htTails^[H] then
|
|
htTails^[H] := P;
|
|
DestroyNode(Self, T, nil);
|
|
Dec(FCount);
|
|
end;
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
LeaveCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
destructor TStHashTable.Destroy;
|
|
var
|
|
TableSize : Cardinal;
|
|
begin
|
|
if conNodeProt = 0 then
|
|
Clear;
|
|
TableSize := FHashSize*SizeOf(TStHashNode);
|
|
if Assigned(htHeads) then
|
|
FreeMem(htHeads, TableSize);
|
|
if Assigned(htTails) then
|
|
FreeMem(htTails, TableSize);
|
|
IncNodeProtection;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TStHashTable.Exists(const V; var Data : Pointer) : Boolean;
|
|
var
|
|
H : Integer;
|
|
P, T : TStHashNode;
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
htFindNode(V, H, P, T);
|
|
if Assigned(T) then begin
|
|
htMoveToFront(H, P, T);
|
|
htUpdateLRU(T);
|
|
Result := True;
|
|
Data := T.Data;
|
|
end else
|
|
Result := False;
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
LeaveCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function FindNodeData(Container : TStContainer; Node : TStNode;
|
|
OtherData : Pointer) : Boolean; far;
|
|
begin
|
|
Result := (OtherData <> Node.Data);
|
|
end;
|
|
|
|
function TStHashTable.Find(Data : Pointer; var V) : Boolean;
|
|
var
|
|
H : Integer;
|
|
P, T : TStHashNode;
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
htIterate(FindNodeData, Data, H, P, T);
|
|
if Assigned(T) then begin
|
|
htMoveToFront(H, P, T);
|
|
htUpdateLRU(T);
|
|
Result := True;
|
|
Move(T.Value^, V, FValSize);
|
|
end else
|
|
Result := False;
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
LeaveCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TStHashTable.htDeleteOldestNode;
|
|
{-Find and delete the hash node with the smallest LRU counter}
|
|
var
|
|
H, MinH : Integer;
|
|
MinLRU : LongInt;
|
|
T, P : TStHashNode;
|
|
begin
|
|
if FCount <> 0 then begin
|
|
MinLRU := MaxLongInt;
|
|
MinH := 0;
|
|
for H := 0 to FHashSize-1 do
|
|
if Assigned(htTails^[H]) and (htTails^[H].LRU <= MinLRU) then begin
|
|
MinH := H;
|
|
MinLRU := htTails^[H].LRU;
|
|
end;
|
|
|
|
{notify the application}
|
|
with htTails^[MinH] do
|
|
NodeRemoved(hnValue^, Data);
|
|
|
|
{destroy the node}
|
|
DestroyNode(Self, htTails^[MinH], nil);
|
|
dec(FCount);
|
|
|
|
{remove the node}
|
|
if htTails^[MinH] = htHeads^[MinH] then begin
|
|
{only node in this bin}
|
|
htTails^[MinH] := nil;
|
|
htHeads^[MinH] := nil;
|
|
end else begin
|
|
{at least two nodes in this bin}
|
|
T := htHeads^[MinH];
|
|
P := nil;
|
|
while T <> htTails^[MinH] do begin
|
|
P := T;
|
|
T := T.hnNext;
|
|
end;
|
|
P.hnNext := nil;
|
|
htTails^[MinH] := P;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TStHashTable.htFindNode(const V; var H : Integer;
|
|
var Prev, This : TStHashNode);
|
|
var
|
|
P, T : TStHashNode;
|
|
begin
|
|
if not(Assigned(FEqual) and Assigned(FHash)) then
|
|
RaiseContainerError(stscNoCompare);
|
|
|
|
Prev := nil;
|
|
This := nil;
|
|
H := FHash(V, HashSize);
|
|
T := htHeads^[H];
|
|
P := nil;
|
|
while Assigned(T) do begin
|
|
if FEqual(V, T.Value^) = 0 then begin
|
|
Prev := P;
|
|
This := T;
|
|
Exit;
|
|
end;
|
|
P := T;
|
|
T := T.hnNext;
|
|
end;
|
|
|
|
{not found}
|
|
This := nil;
|
|
end;
|
|
|
|
procedure TStHashTable.htInsertNode(H : Integer; This : TStHashNode);
|
|
{-Insert node This at front of hash bin H}
|
|
var
|
|
P : TStHashNode;
|
|
begin
|
|
P := htHeads^[H];
|
|
htHeads^[H] := This;
|
|
if not Assigned(htTails^[H]) then
|
|
htTails^[H] := This;
|
|
This.hnNext := P;
|
|
htUpdateLRU(This);
|
|
Inc(FCount);
|
|
if FCount > FMaxNodes then
|
|
htDeleteOldestNode;
|
|
end;
|
|
|
|
procedure TStHashTable.htIterate(Action : TIterateFunc; OtherData : Pointer;
|
|
var H : Integer; var Prev, This : TStHashNode);
|
|
{-Internal version of Iterate that returns more details}
|
|
var
|
|
AHash : Integer;
|
|
P, T, N : TStHashNode;
|
|
begin
|
|
if FCount <> 0 then begin
|
|
for AHash := 0 to FHashSize-1 do begin
|
|
T := htHeads^[AHash];
|
|
P := nil;
|
|
while Assigned(T) do begin
|
|
N := T.hnNext;
|
|
if Action(Self, T, OtherData) then begin
|
|
P := T;
|
|
T := N;
|
|
end else begin
|
|
H := AHash;
|
|
Prev := P;
|
|
This := T;
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
This := nil;
|
|
end;
|
|
|
|
procedure TStHashTable.htMoveToFront(H : Integer; Prev, This : TStHashNode);
|
|
{-Move This to front of list}
|
|
begin
|
|
if Assigned(Prev) then begin
|
|
Prev.hnNext := This.hnNext;
|
|
This.hnNext := htHeads^[H];
|
|
htHeads^[H] := This;
|
|
if This = htTails^[H] then
|
|
htTails^[H] := Prev;
|
|
end;
|
|
end;
|
|
|
|
procedure TStHashTable.htSetEqual(E : TUntypedCompareFunc);
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
if Count = 0 then
|
|
FEqual := E;
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
LeaveCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TStHashTable.htSetHash(H : THashFunc);
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
if Count = 0 then
|
|
FHash := H;
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
LeaveCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TStHashTable.htSetHashSize(Size : Integer);
|
|
var
|
|
HInx : integer;
|
|
TableSize: LongInt;
|
|
Temp : TStHashNode;
|
|
Node : TStHashNode;
|
|
OldHeads : PHashArray;
|
|
OldTails : PHashArray;
|
|
OldSize : Integer;
|
|
OldCount : Integer;
|
|
OldDisposeData : TDisposeDataProc;
|
|
OldOnDisposeData : TStDisposeDataEvent;
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
{calculate the new table size}
|
|
TableSize := LongInt(Size) * sizeof(TStHashNode);
|
|
if (Size <= 0) {or (TableSize > MaxBlockSize)} then
|
|
RaiseContainerError(stscBadSize);
|
|
|
|
{only do something if there's something to do}
|
|
if (Size <> FHashSize) then begin
|
|
|
|
{Notes: lots of things are going to be happening here: new
|
|
allocations, nodes copied from the old table to the new,
|
|
etc. Ideally if an exception is raised we would like to
|
|
restore the hash table to the state it was in
|
|
originally, before letting the exception escape}
|
|
|
|
{save enough data about the current state of the table to
|
|
allow restoring in case of an exception}
|
|
OldHeads := htHeads;
|
|
OldTails := htTails;
|
|
OldSize := FHashSize;
|
|
OldCount := FCount;
|
|
OldDisposeData := DisposeData;
|
|
OldOnDisposeData := OnDisposeData;
|
|
|
|
{reset Self's data}
|
|
htHeads := nil;
|
|
htTails := nil;
|
|
FHashSize := Size;
|
|
FCount := 0;
|
|
DisposeData := nil;
|
|
OnDisposeData := nil;
|
|
|
|
{from this point, exceptions can occur with impunity...}
|
|
try
|
|
{allocate the new head and tail tables}
|
|
htHeads := AllocMem(TableSize);
|
|
htTails := AllocMem(TableSize);
|
|
|
|
{if there is data to transfer, do so}
|
|
if (OldHeads <> nil) and (OldCount <> 0) then begin
|
|
for HInx := 0 to pred(OldSize) do begin
|
|
Node := OldHeads^[HInx];
|
|
while Assigned(Node) do begin
|
|
Add(Node.hnValue^, Node.Data);
|
|
Node := Node.hnNext;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{now all the data has been transferred, we can
|
|
destroy the old table}
|
|
if (OldHeads <> nil) then begin
|
|
for HInx := 0 to pred(OldSize) do begin
|
|
Node := OldHeads^[HInx];
|
|
while Assigned(Node) do begin
|
|
Temp := Node;
|
|
Node := Node.hnNext;
|
|
Temp.Free;
|
|
end;
|
|
end;
|
|
FreeMem(OldHeads, OldSize * sizeof(TStHashNode));
|
|
end;
|
|
if (OldTails <> nil) then
|
|
FreeMem(OldTails, OldSize * sizeof(TStHashNode));
|
|
|
|
{restore the disposedata routines}
|
|
DisposeData := OldDisposeData;
|
|
OnDisposeData := OldOnDisposeData;
|
|
|
|
except
|
|
{destroy the new data}
|
|
if (htHeads <> nil) then begin
|
|
for HInx := 0 to pred(FHashSize) do begin
|
|
Node := htHeads^[HInx];
|
|
while Assigned(Node) do begin
|
|
Temp := Node;
|
|
Node := Node.hnNext;
|
|
Temp.Free;
|
|
end;
|
|
end;
|
|
FreeMem(htHeads, TableSize);
|
|
end;
|
|
if (htTails <> nil) then
|
|
FreeMem(htTails, TableSize);
|
|
{restore the old data}
|
|
htHeads := OldHeads;
|
|
htTails := OldTails;
|
|
FHashSize := OldSize;
|
|
FCount := OldCount;
|
|
DisposeData := OldDisposeData;
|
|
OnDisposeData := OldOnDisposeData;
|
|
{reraise the exception}
|
|
raise;
|
|
end;
|
|
end;
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
LeaveCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TStHashTable.htSetMaxNodes(Nodes : LongInt);
|
|
begin
|
|
if Nodes < 1 then
|
|
RaiseContainerError(stscBadSize);
|
|
FMaxNodes := Nodes;
|
|
while FCount > FMaxNodes do
|
|
htDeleteOldestNode;
|
|
end;
|
|
|
|
type
|
|
TMinNode = record
|
|
MLRU : LongInt;
|
|
MNode : TStHashNode;
|
|
end;
|
|
PMinNode = ^TMinNode;
|
|
|
|
function FindMinPositiveNode(Container : TStContainer;
|
|
Node : TStNode;
|
|
OtherData : Pointer) : Boolean; far;
|
|
{-Used to find the smallest non-negative LRU in the table}
|
|
begin
|
|
with PMinNode(OtherData)^, TStHashNode(Node) do
|
|
if (LRU >= 0) and (LRU <= MLRU) then begin
|
|
MLRU := LRU;
|
|
MNode := TStHashNode(Node);
|
|
end;
|
|
Result := True;
|
|
end;
|
|
|
|
function NegateNodeLRU(Container : TStContainer;
|
|
Node : TStNode;
|
|
OtherData : Pointer) : Boolean; far;
|
|
{-Used to negate the LRU values of all nodes in the table}
|
|
begin
|
|
with TStHashNode(Node) do
|
|
LRU := -LRU;
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TStHashTable.htUpdateLRU(This : TStHashNode);
|
|
{-Reassign all LRU values sequentially in their existing order}
|
|
var
|
|
MinNode : TMinNode;
|
|
begin
|
|
inc(htLRU);
|
|
This.LRU := htLRU;
|
|
if htLRU = MaxLongInt then begin
|
|
{scan table and pack LRU values}
|
|
htLRU := 0;
|
|
repeat
|
|
inc(htLRU);
|
|
MinNode.MLRU := MaxLongInt;
|
|
MinNode.MNode := nil;
|
|
Iterate(FindMinPositiveNode, @MinNode);
|
|
if not Assigned(MinNode.MNode) then
|
|
break;
|
|
{nodes already visited are set to a negative value}
|
|
{depends on never having an LRU of zero}
|
|
MinNode.MNode.LRU := -htLRU;
|
|
until False;
|
|
{negative values are made positive}
|
|
Iterate(NegateNodeLRU, nil);
|
|
end;
|
|
end;
|
|
|
|
function TStHashTable.Iterate(Action : TIterateFunc;
|
|
OtherData : Pointer) : TStHashNode;
|
|
var
|
|
H : Integer;
|
|
P : TStHashNode;
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
htIterate(Action, OtherData, H, P, Result);
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
LeaveCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function JoinNode(Container : TStContainer;
|
|
Node : TStNode;
|
|
OtherData : Pointer) : Boolean; far;
|
|
{-Used to add nodes from another table into this one}
|
|
var
|
|
H : Integer;
|
|
P, T : TStHashNode;
|
|
begin
|
|
Result := True;
|
|
with TStHashTable(OtherData) do begin
|
|
htFindNode(TStHashNode(Node).Value^, H, P, T);
|
|
if Assigned(T) then
|
|
if htIgnoreDups then begin
|
|
Node.Free;
|
|
Exit;
|
|
end else
|
|
RaiseContainerError(stscDupNode);
|
|
htInsertNode(H, TStHashNode(Node));
|
|
end;
|
|
end;
|
|
|
|
procedure TStHashTable.Join(H : TStHashTable; IgnoreDups : Boolean);
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterClassCS;
|
|
EnterCS;
|
|
H.EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
htIgnoreDups := IgnoreDups;
|
|
H.Iterate(JoinNode, Self);
|
|
{dispose of D, but not its nodes}
|
|
H.IncNodeProtection;
|
|
H.Free;
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
H.LeaveCS;
|
|
LeaveCS;
|
|
LeaveClassCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TStHashTable.LoadFromStream(S : TStream);
|
|
var
|
|
Data, Value : Pointer;
|
|
AValSize : Cardinal;
|
|
Reader : TReader;
|
|
StreamedClass : TPersistentClass;
|
|
StreamedNodeClass : TPersistentClass;
|
|
StreamedClassName : string;
|
|
StreamedNodeClassName : string;
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
Clear;
|
|
Reader := TReader.Create(S, 1024);
|
|
try
|
|
with Reader do begin
|
|
StreamedClassName := ReadString;
|
|
StreamedClass := GetClass(StreamedClassName);
|
|
if not Assigned(StreamedClass) then
|
|
RaiseContainerErrorFmt(stscUnknownClass, [StreamedClassName]);
|
|
if (StreamedClass <> Self.ClassType) then
|
|
RaiseContainerError(stscWrongClass);
|
|
StreamedNodeClassName := ReadString;
|
|
StreamedNodeClass := GetClass(StreamedNodeClassName);
|
|
if not Assigned(StreamedNodeClass) then
|
|
RaiseContainerErrorFmt(stscUnknownNodeClass, [StreamedNodeClassName]);
|
|
if (StreamedNodeClass <> conNodeClass) then
|
|
RaiseContainerError(stscWrongNodeClass);
|
|
|
|
AValSize := ReadInteger;
|
|
if AValSize <> FValSize then
|
|
RaiseContainerError(stscBadSize);
|
|
HashSize := ReadInteger;
|
|
FMaxNodes := ReadInteger;
|
|
GetMem(Value, FValSize);
|
|
try
|
|
ReadListBegin;
|
|
while not EndOfList do begin
|
|
ReadBoolean;
|
|
Read(Value^, FValSize);
|
|
Data := DoLoadData(Reader);
|
|
Add(Value^, Data);
|
|
end;
|
|
ReadListEnd;
|
|
finally
|
|
FreeMem(Value, FValSize);
|
|
end;
|
|
end;
|
|
finally
|
|
Reader.Free;
|
|
end;
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
LeaveCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TStHashTable.NodeRemoved(const V; Data : Pointer);
|
|
begin
|
|
{does nothing by default}
|
|
end;
|
|
|
|
procedure TStHashTable.StoreToStream(S : TStream);
|
|
var
|
|
H : Integer;
|
|
Walker : TStHashNode;
|
|
Writer : TWriter;
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
Writer := TWriter.Create(S, 1024);
|
|
try
|
|
with Writer do begin
|
|
WriteString(Self.ClassName);
|
|
WriteString(conNodeClass.ClassName);
|
|
WriteInteger(FValSize);
|
|
WriteInteger(FHashSize);
|
|
WriteInteger(FMaxNodes);
|
|
WriteListBegin;
|
|
if Count <> 0 then
|
|
for H := 0 to FHashSize-1 do begin
|
|
Walker := htHeads^[H];
|
|
while Assigned(Walker) do begin
|
|
{writing the True boolean prevents false termination of the
|
|
list if Value's first byte is zero when the stream is
|
|
loaded into another hash table}
|
|
WriteBoolean(True);
|
|
Write(Walker.Value^, FValSize);
|
|
DoStoreData(Writer, Walker.Data);
|
|
Walker := Walker.hnNext;
|
|
end;
|
|
end;
|
|
WriteListEnd;
|
|
end;
|
|
finally
|
|
Writer.Free;
|
|
end;
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
LeaveCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TStHashTable.Update(const V; Data : Pointer);
|
|
var
|
|
H : Integer;
|
|
P, T : TStHashNode;
|
|
begin
|
|
{$IFDEF ThreadSafe}
|
|
EnterCS;
|
|
try
|
|
{$ENDIF}
|
|
htFindNode(V, H, P, T);
|
|
if Assigned(T) then begin
|
|
htMoveToFront(H, P, T);
|
|
htUpdateLRU(T);
|
|
T.Data := Data;
|
|
end;
|
|
{$IFDEF ThreadSafe}
|
|
finally
|
|
LeaveCS;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{$IFDEF ThreadSafe}
|
|
initialization
|
|
Windows.InitializeCriticalSection(ClassCritSect);
|
|
finalization
|
|
Windows.DeleteCriticalSection(ClassCritSect);
|
|
{$ENDIF}
|
|
end.
|