{ +-------------------------------------------------------------+ } { | | } { | GM-Software | } { | =========== | } { | | } { | Project: All Projects | } { | | } { | Description: Collections/Containers, ArrayLists, AVL- | } { | Trees and HashTables with common interfaces. | } { | All Collections are thread safe! | } { | | } { | Copyright (C) - 2011 - Gerrit Moeller. | } { | | } { | Source code distributed under MIT license. | } { | | } { | See: https://www.gm-software.de | } { | | } { +-------------------------------------------------------------+ } {$INCLUDE GMCompilerSettings.inc} {.$DEFINE STANDALONE_COLLECTIONS} // <- avoid using GMIntf and GMCommon units unit GMCollections; interface uses GMStrDef, {$IFNDEF JEDIAPI}Windows{$ELSE}{$IFNDEF FPC}jwaWinType,{$ENDIF} jwaWinError{$ENDIF} {$IFNDEF STANDALONE_COLLECTIONS}, GMIntf{$ENDIF}; const cMaxPtrArraySize = High(PtrInt) div SizeOf(Pointer); cMaxIntArraySize = High(PtrInt) div SizeOf(PtrInt); cGrowDeltaDiv = 5; // <- 20% cGrowDeltaMax = 1024; { ------------------------------------------------------------------- } { ---- Re-defined types when not using GMIntf and GMCommon units ---- } { ------------------------------------------------------------------- } {$IFDEF STANDALONE_COLLECTIONS} const cInvalidItemIdx = Low(LongInt); // -1; type TGMCompareResult = (crALessThanB, crAEqualToB, crAGreaterThanB); {$IFNDEF FPC} {$IFDEF CPU64} PtrInt = Int64; PtrUInt = QWord; {$ELSE} PtrInt = LongInt; PtrUInt = LongWord; {$ENDIF} PPtrInt = ^PtrInt; PPtrUInt = ^PtrUInt; {$ENDIF} TGMHashCode = PtrInt; IGMCriticalSection = interface(IUnknown) ['{278BDF06-1387-4181-A83D-8DDF4E18CE03}'] procedure EnterCriticalSection; procedure LeaveCriticalSection; //function TryEnterCriticalSection: Boolean; stdcall; end; IGMGetCount = interface(IUnknown) ['{93880081-2684-11d5-AB38-000021DCAD19}'] function GetCount: PtrInt; stdcall; property Count: PtrInt read GetCount; end; IGMLoadStoreData = interface(IUnknown) ['{D8D48DE1-AE80-4132-AE40-ECA66F9256C6}'] procedure LoadData(const Source: IUnknown); stdcall; procedure StoreData(const Dest: IUnknown); stdcall; end; IGMGetIntfByPosition = interface(IUnknown) ['{4694A884-24F6-11d5-AB38-000021DCAD19}'] function GetIntfByPosition(const Position: PtrInt; const IID: TGUID; out Intf): HResult; stdcall; end; IGMMapIntegerOnInteger = interface(IUnknown) ['{1BC6F7EB-C230-48ba-B383-B332D555DA6F}'] function MapIntegerOnInteger(const MapValue: PtrInt): PtrInt; stdcall; end; IGMHashCode = interface(IUnknown) ['{9C61B58B-41DF-4695-9716-AC4A343DC2DB}'] function HashCode: TGMHashCode; end; TGMRefCountedObj = class(TObject, IUnknown) // IGMObjInfo, IGMCreateCopyQI protected FRefLifeTime: Boolean; FRefCount: LongInt; public constructor Create(const ARefLifeTime: Boolean = False); virtual; procedure AfterConstruction; override; procedure BeforeDestruction; override; // destructor Destroy; override; procedure OnFinalRelease; virtual; // IGMObjInfo // function GetClassName: PChar; // function GetClassType: TClass; // function GetInstance: TObject; // function GetTypeInfo: PTypeInfo; // IUnknown function QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} AIID: TGUID; out AIntf): HResult; virtual; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; function _AddRef: LongInt; virtual; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; function _Release: LongInt; virtual; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; // IGMCreateCopyQI // function CreateCopyQI(const IID: TGUID; out Intf): HResult; virtual; stdcall; property RefCount: LongInt read FRefCount; property RefLifeTime: Boolean read FRefLifeTime write FRefLifeTime; end; TGMCriticalSection = class(TGMRefCountedObj, IGMCriticalSection) protected FCriticalSection: TRTLCriticalSection; public constructor Create(const ARefLifeTime: Boolean = True); override; destructor Destroy; override; procedure EnterCriticalSection; procedure LeaveCriticalSection; function TryEnterCriticalSection: Boolean; end; TGMCriticalSectionLock = class(TGMRefCountedObj) protected FCriticalSection: IGMCriticalSection; public constructor Create(const ACriticalSection: IUnknown; const ARefLifeTime: Boolean = True); reintroduce; destructor Destroy; override; end; {$ENDIF} { ----------------------------- } { ---- Collection Gerneral ---- } { ----------------------------- } type TGMIntfCompareFunc = function (const EntryA, EntryB: IUnknown): TGMCompareResult; TGMCountChangedProc = procedure (const Sender: TObject; const OldCount, NewCount: PtrInt) of Object; //TGMObjVisitFunc = function (const VisitedObj: TObject): Boolean; IGMIterator = interface(IUnknown) ['{A78717F7-120B-4704-BCA3-2C9E8706CE48}'] function NextEntry(out AEntry): Boolean; procedure Reset; end; IGMCreateIterator = interface(IGMGetCount) // IUnknown ['{E57F2FDF-8660-448E-968D-C7248A20EA24}'] function CreateIterator(const AReverse: Boolean = False; const AConcurrentThreadLock: Boolean = False): IGMIterator; end; IGMCollection = interface(IGMCreateIterator) ['{468D1111-7D28-4195-9C44-13065516B0F4}'] procedure SetCompareFunc(const Value: TGMIntfCompareFunc); //function GetCount: LongInt; stdcall; function IsEmpty: Boolean; procedure SetOnAfterCountChanged(const Value: TGMCountChangedProc); procedure Clear(const ANotify: Boolean = True); function Find(const AKeyToCompare: IUnknown; out AEntry): Boolean; function FindNearest(const AKeyToCompare: IUnknown; out AEntry): Boolean; function RemoveByKey(const AKeyToCompare: IUnknown): Boolean; function IsValidIndex(const AIndex: PtrInt): Boolean; function CompareEntries(const EntryA, EntryB: IUnknown): TGMCompareResult; property Count: PtrInt read GetCount; property CompareItemFunc: TGMIntfCompareFunc write SetCompareFunc; property OnAfterCountChanged: TGMCountChangedProc write SetOnAfterCountChanged; end; IGMObjCollection = interface(IGMCollection) ['{979CCBB6-587C-49FD-A98A-0D3DF11789E0}'] function First: TObject; function Last: TObject; function Add(const ANewEntry: TObject; const AReplaceIfExists: Boolean = False): TObject; function RemoveByInstance(const AObj: TObject): Boolean; end; IGMIntfCollection = interface(IGMCollection) ['{3DEA459B-0A78-4C91-981E-2E6F4D5C5D9F}'] function First: IUnknown; function Last: IUnknown; function Add(const ANewEntry: IUnknown; const AReplaceIfExists: Boolean = False): IUnknown; function RemoveByInstance(const AIntf: IUnknown): Boolean; end; TGMIteratorBase = class(TGMRefCountedObj, IGMIterator) protected FCollection: TObject; FReverse: Boolean; //FThreadLock: IUnknown; FThreadLock: RGMCriticalSectionLock; public constructor Create(const ACollection: TObject; const AReverse: Boolean = False; const AConcurrentThreadLock: Boolean = False; const ARefLifeTime: Boolean = True); reintroduce; function NextEntry(out AEntry): Boolean; virtual; abstract; procedure Reset; virtual; abstract; end; TGMCollectionBase = class(TGMRefCountedObj, IGMCriticalSection, IGMCollection, IGMGetCount, IGMCreateIterator, IGMClear) protected FCount: PtrInt; FCompareFunc: TGMIntfCompareFunc; FCriticalSection: IGMCriticalSection; FAcceptDuplicates: Boolean; FOnAfterCountChanged: TGMCountChangedProc; function CompareEntries(const EntryA, EntryB: IUnknown): TGMCompareResult; procedure SetCompareFunc(const AValue: TGMIntfCompareFunc); virtual; procedure AfterCompareFuncChanged; virtual; procedure NotifyAfterCountChanged(const OldCount, NewCount: PtrInt); procedure SetOnAfterCountChanged(const Value: TGMCountChangedProc); public constructor Create(const AAcceptDuplicates: Boolean = True; const ACompareFunc: TGMIntfCompareFunc = nil; const ARefLifeTime: Boolean = False); reintroduce; overload; virtual; destructor Destroy; override; function GetCount: PtrInt; virtual; stdcall; function IsEmpty: Boolean; virtual; function IsValidIndex(const AIndex: PtrInt): Boolean; virtual; procedure Clear(const ANotify: Boolean = True); virtual; abstract; function Find(const AKeyToCompare: IUnknown; out AEntry): Boolean; virtual; abstract; function FindNearest(const AKeyToCompare: IUnknown; out AEntry): Boolean; virtual; abstract; function RemoveByKey(const AKeyToCompare: IUnknown): Boolean; virtual; abstract; function CreateIterator(const AReverse: Boolean = False; const AConcurrentThreadLock: Boolean = False): IGMIterator; virtual; abstract; property Count: PtrInt read GetCount; property CompareItemFunc: TGMIntfCompareFunc read FCompareFunc write SetCompareFunc; property CriticalSection: IGMCriticalSection read FCriticalSection implements IGMCriticalSection; property OnAfterCountChanged: TGMCountChangedProc read FOnAfterCountChanged write SetOnAfterCountChanged; end; { --------------------------- } { ---- Array Collections ---- } { --------------------------- } // // NOTE: Indexing of all array implementations is zero based! // PPointerArray = ^TPointerArray; TPointerArray = array[0 .. cMaxPtrArraySize-1] of Pointer; TGMArrayCollectionBase = class(TGMCollectionBase, IGMGetIntfByPosition, IGMLoadStoreData) protected FEntries: PPointerArray; FCapacity: PtrInt; FSorted: Boolean; FFreeEntries: Boolean; function GetSorted: Boolean; procedure SetSorted(const AValue: Boolean); procedure SetCapacity(const ANewCapacity: PtrInt); function IndexOfPointer(const Ptr: Pointer): PtrInt; function IsDuplicate(const AKeyToCompare: IUnknown; var AIndex: PtrInt): Boolean; procedure InsertPointer(const AInstance: Pointer; const AIndex: PtrInt); function EntryAsIntf(const Index: PtrInt): IUnknown; virtual; abstract; procedure FreePointer(var Item: Pointer); virtual; abstract; procedure AfterCompareFuncChanged; override; procedure NotifyAfterAddItem(const AItem: Pointer; const AIndex: PtrInt); virtual; abstract; procedure NotifyBeforeRemoveItem(const AItem: Pointer; const AIndex: PtrInt); virtual; abstract; procedure CheckSorted(const AOperationName: TGMString); procedure CheckUnsorted(const AOperationName: TGMString); public constructor Create(const AAcceptDuplicates: Boolean = True; const ASorted: Boolean = False; const ACompareFunc: TGMIntfCompareFunc = nil; const ARefLifeTime: Boolean = False); reintroduce; function GetIntfByPosition(const APosition: PtrInt; const AIID: TGUID; out AIntf): HResult; virtual; stdcall; procedure Clear(const ANotify: Boolean = True); override; procedure Sort; virtual; //procedure Pack; virtual; {$IFDEF STANDALONE_COLLECTIONS} procedure LoadData(const Source: IUnknown; const ACryptCtrlData: PGMCryptCtrlData = nil); stdcall; procedure StoreData(const Dest: IUnknown; const ACryptCtrlData: PGMCryptCtrlData = nil); stdcall; {$ELSE} procedure LoadData(const Source: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); stdcall; procedure StoreData(const Dest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); stdcall; {$ENDIF} procedure RemoveByIdx(const AIndex: PtrInt; ADelCount: PtrInt = 1); virtual; function RemoveByKey(const AKeyToCompare: IUnknown): Boolean; override; function CalcInsertIdx(const AKeyToCompare: IUnknown): PtrInt; virtual; function IndexOf(const AKeyToCompare: IUnknown): PtrInt; virtual; function IndexOfNearest(const AKeyToCompare: IUnknown): PtrInt; virtual; function Find(const AKeyToCompare: IUnknown; out AEntry): Boolean; override; function FindNearest(const AKeyToCompare: IUnknown; out AEntry): Boolean; override; procedure Rotate(ADelta: PtrInt; const AStartIdx: PtrInt = 0); procedure Exchange(const AIndex1, AIndex2: PtrInt); //procedure Move(const ASourceIdx, ADestIdx: PtrInt); procedure Reverse; property Capacity: PtrInt read FCapacity {write SetCapacity}; property Sorted: Boolean read GetSorted write SetSorted; end; TGMArrayCollectionIteratorBase = class(TGMIteratorBase) protected FCurrentIdx: PtrInt; procedure AssignOutEntry(out AEntry); virtual; abstract; public procedure Reset; override; function NextEntry(out AEntry): Boolean; override; end; TGMObjItemAddRemoveProc = procedure(const Sender, Entry: TObject; const AIndex: PtrInt) of object; IGMObjArrayCollection = interface(IGMObjCollection) ['{E83BD4DC-E50E-4fc3-9161-9F5829BC6C92}'] function GetItem(const Index: PtrInt): TObject; procedure SetItem(const Index: PtrInt; const Value: TObject); procedure SetOnAfterAddItem(const Value: TGMObjItemAddRemoveProc); procedure SetOnBeforeRemoveItem(const Value: TGMObjItemAddRemoveProc); //function Add(const AObj: TObject): TObject; function AddIdx(const AObj: TObject; const AReplaceIfExists: Boolean = False): PtrInt; function Insert(const AObj: TObject; const AIndex: PtrInt; const AReplaceIfExists: Boolean = False): TObject; //function First: TObject; //function Last: TObject; function IndexOfObj(const AObj: TObject): PtrInt; //function FindObj(const AObj: TObject; var Index: LongInt): Boolean; property Items[const Index: PtrInt]: TObject read GetItem write SetItem; default; property OnAfterAddItem: TGMObjItemAddRemoveProc write SetOnAfterAddItem; property OnBeforeRemoveItem: TGMObjItemAddRemoveProc write SetOnBeforeRemoveItem; // ---- ArrayListBase ---- // function GetSorted: Boolean; procedure SetSorted(const Value: Boolean); procedure RemoveByIdx(const Index: PtrInt; DelCount: PtrInt = 1); procedure Rotate(Delta: PtrInt; const StartPos: PtrInt = 0); procedure Exchange(const Index1, Index2: PtrInt); procedure Reverse; procedure Sort; function IndexOf(const AKeyToCompare: IUnknown): PtrInt; function IndexOfNearest(const AKeyToCompare: IUnknown): PtrInt; property Sorted: Boolean read GetSorted write SetSorted; end; TGMObjArrayCollection = class(TGMArrayCollectionBase, IGMObjArrayCollection, IGMObjCollection) protected function ObjInsertIdx(const AObj: TObject): PtrInt; function InternalInsert(const AObj: TObject; var AIndex: PtrInt; const AReplaceIfExists: Boolean = False): Boolean; virtual; function GetItem(const AIndex: PtrInt): TObject; virtual; function EntryAsIntf(const AIndex: PtrInt): IUnknown; override; procedure SetItem(const AIndex: PtrInt; const AValue: TObject); virtual; procedure FreePointer(var AItem: Pointer); override; procedure SetOnAfterAddItem(const AValue: TGMObjItemAddRemoveProc); procedure SetOnBeforeRemoveItem(const Value: TGMObjItemAddRemoveProc); procedure NotifyAfterAddItem(const AItem: Pointer; const AIndex: PtrInt); override; procedure NotifyBeforeRemoveItem(const AItem: Pointer; const AIndex: PtrInt); override; public OnAfterAddItem: TGMObjItemAddRemoveProc; OnBeforeRemoveItem: TGMObjItemAddRemoveProc; constructor Create(const AFreeEntries: Boolean = False; const AAcceptDuplicates: Boolean = True; const ASorted: Boolean = False; const ACompareFunc: TGMIntfCompareFunc = nil; const ARefLifeTime: Boolean = False); function CreateIterator(const AReverse: Boolean = False; const AConcurrentThreadLock: Boolean = False): IGMIterator; override; function Add(const AObj: TObject; const AReplaceIfExists: Boolean = False): TObject; virtual; function AddIdx(const AObj: TObject; const AReplaceIfExists: Boolean = False): PtrInt; virtual; function Insert(const AObj: TObject; const AIndex: PtrInt; const AReplaceIfExists: Boolean = False): TObject; virtual; function First: TObject; virtual; function Last: TObject; virtual; function IndexOfObj(const AObj: TObject): PtrInt; function RemoveByInstance(const AObj: TObject): Boolean; //function FindObj(const AObj: TObject; var Index: LongInt): Boolean; virtual; property Entries[const Index: PtrInt]: TObject read GetItem write SetItem; default; // <- Indexing is zero based! property FreeEntries: Boolean read FFreeEntries write FFreeEntries; end; TGMObjectCollectionIterator = class(TGMArrayCollectionIteratorBase) protected procedure AssignOutEntry(out AEntry); override; end; TGMIntfItemAddRemoveProc = procedure(const Sender: TObject; const Item: IUnknown; const AIndex: LongInt) of object; IGMIntfArrayCollection = interface(IGMIntfCollection) ['{E83BD4DC-E50E-4fc3-9161-9F5829BC6C92}'] function GetItem(const Index: PtrInt): IUnknown; procedure SetOnAfterAddItem(const Value: TGMIntfItemAddRemoveProc); procedure SetOnBeforeRemoveItem(const Value: TGMIntfItemAddRemoveProc); //function Add(const AIntf: IUnknown): IUnknown; function AddIdx(const AIntf: IUnknown; const AReplaceIfExists: Boolean = False): PtrInt; function Insert(const AIntf: IUnknown; const AIndex: PtrInt; const AReplaceIfExists: Boolean = False): IUnknown; //function Remove(const AIntf: IUnknown): LongInt; //function First: IUnknown; //function Last: IUnknown; function IndexOfObj(const AIntf: IUnknown): PtrInt; //function FindObj(const AIntf: IUnknown; var Index: LongInt): Boolean; property Items[const Index: PtrInt]: IUnknown read GetItem; default; property OnAfterAddItem: TGMIntfItemAddRemoveProc write SetOnAfterAddItem; property OnBeforeRemoveItem: TGMIntfItemAddRemoveProc write SetOnBeforeRemoveItem; // ---- ArrayListBase ---- // function GetSorted: Boolean; procedure SetSorted(const Value: Boolean); procedure RemoveByIdx(const Index: PtrInt; DelCount: PtrInt = 1); procedure Rotate(Delta: PtrInt; const StartPos: PtrInt = 0); procedure Exchange(const Index1, Index2: PtrInt); procedure Reverse; procedure Sort; function IndexOf(const AKeyToCompare: IUnknown): PtrInt; function IndexOfNearest(const AKeyToCompare: IUnknown): PtrInt; property Sorted: Boolean read GetSorted write SetSorted; end; TGMIntfArrayCollection = class(TGMArrayCollectionBase, IGMIntfCollection, IGMIntfArrayCollection) protected function InternalInsert(const AIntf: IUnknown; var AIndex: PtrInt; const AReplaceIfExists: Boolean = False): Boolean; virtual; function GetItem(const Index: PtrInt): IUnknown; virtual; function EntryAsIntf(const AIndex: PtrInt): IUnknown; override; procedure FreePointer(var AItem: Pointer); override; procedure SetOnAfterAddItem(const AValue: TGMIntfItemAddRemoveProc); procedure SetOnBeforeRemoveItem(const Value: TGMIntfItemAddRemoveProc); procedure NotifyAfterAddItem(const AItem: Pointer; const AIndex: PtrInt); override; procedure NotifyBeforeRemoveItem(const AItem: Pointer; const AIndex: PtrInt); override; public OnAfterAddItem: TGMIntfItemAddRemoveProc; OnBeforeRemoveItem: TGMIntfItemAddRemoveProc; Constructor Create(const AAcceptDuplicates: Boolean = True; const ASorted: Boolean = False; const ACompareFunc: TGMIntfCompareFunc = nil; const ARefLifeTime: Boolean = True); function CreateIterator(const AReverse: Boolean = False; const AConcurrentThreadLock: Boolean = False): IGMIterator; override; function Add(const AIntf: IUnknown; const AReplaceIfExists: Boolean = False): IUnknown; virtual; function AddIdx(const AIntf: IUnknown; const AReplaceIfExists: Boolean = False): PtrInt; virtual; function Insert(const AIntf: IUnknown; const AIndex: PtrInt; const AReplaceIfExists: Boolean = False): IUnknown; virtual; function Find(const AKeyToCompare: IUnknown; out AEntry): Boolean; override; function FindNearest(const AKeyToCompare: IUnknown; out AEntry): Boolean; override; function First: IUnknown; virtual; function Last: IUnknown; virtual; function IndexOfObj(const AIntf: IUnknown): PtrInt; function RemoveByInstance(const AIntf: IUnknown): Boolean; //function FindObj(const Intf: IUnknown; var Index: LongInt): Boolean; virtual; property Entries[const Index: PtrInt]: IUnknown read GetItem; default; // <- Indexing is zero based! end; TGMIntfArrayCollectionIterator = class(TGMArrayCollectionIteratorBase) protected procedure AssignOutEntry(out AEntry); override; end; { ------------------- } { ---- AVL Trees ---- } { ------------------- } TGMAvlTreeNodeDirection = (tndLeft, tndRight); TGMAvlTreeNodeBase = class public Parent: TGMAvlTreeNodeBase; // , Left, Right Children: array [TGMAvlTreeNodeDirection] of TGMAvlTreeNodeBase; Balance: LongInt; procedure ResetMembers; function TreeHeight: LongInt; function CalcBalance: LongInt; function GetDataAsIntf: IUnknown; virtual; abstract; function IsDataInstance(const AInstance): Boolean; virtual; abstract; function GetChild(const AIdx: TGMAvlTreeNodeDirection): TGMAvlTreeNodeBase; procedure SetChild(const AIdx: TGMAvlTreeNodeDirection; const AValue: TGMAvlTreeNodeBase); property Child [const Index: TGMAvlTreeNodeDirection]: TGMAvlTreeNodeBase read GetChild write SetChild; default; property Left: TGMAvlTreeNodeBase read Children[tndLeft] write Children[tndLeft]; property Right: TGMAvlTreeNodeBase read Children[tndRight] write Children[tndRight]; end; TGMAvlTreeNodeClass = class of TGMAvlTreeNodeBase; TGMAvlTreeBase = class(TGMCollectionBase) protected function TreeNodeCreateClass: TGMAvlTreeNodeClass; virtual; abstract; function CreateTreeNode: TGMAvlTreeNodeBase; virtual; procedure FreeNode(const ANode: TGMAvlTreeNodeBase); virtual; function Rotate(const ANode: TGMAvlTreeNodeBase; const ADirection: TGMAvlTreeNodeDirection): TGMAvlTreeNodeBase; function DoubleRotate(const ANode: TGMAvlTreeNodeBase; const ADirection: TGMAvlTreeNodeDirection): TGMAvlTreeNodeBase; procedure BalanceAfterInsert(const ANode: TGMAvlTreeNodeBase); procedure BalanceAfterDelete(const ANode: TGMAvlTreeNodeBase); function FindInsertPos(const AKeyToCompare: IUnknown): TGMAvlTreeNodeBase; procedure SetCompareFunc(const AValue: TGMIntfCompareFunc); override; function AddNode(const ANode: TGMAvlTreeNodeBase; const AReplaceIfExists: Boolean): Boolean; function FindNode(const AKeyToCompare: IUnknown): TGMAvlTreeNodeBase; function FindNearestNode(const AKeyToCompare: IUnknown): TGMAvlTreeNodeBase; procedure DeleteNode(const ANode: TGMAvlTreeNodeBase); function RemoveByInstance(const AInstance): Boolean; function SuccessorNode(const ANode: TGMAvlTreeNodeBase): TGMAvlTreeNodeBase; function PredecessorNode(const ANode: TGMAvlTreeNodeBase): TGMAvlTreeNodeBase; function FirstNode: TGMAvlTreeNodeBase; function LastNode: TGMAvlTreeNodeBase; public Root: TGMAvlTreeNodeBase; function RemoveByKey(const AKeyToCompare: IUnknown): Boolean; override; procedure Clear(const ANotify: Boolean = True); override; procedure CheckIntegrity; end; //EAvlTreeInconsistency = class(Exception); TGMAvlTreeIteratorBase = class(TGMIteratorBase) protected FCurrentNode: TGMAvlTreeNodeBase; procedure AssignOutEntry(out AEntry); virtual; abstract; public procedure Reset; override; function NextEntry(out AEntry): Boolean; override; end; TGMAvlObjTreeNode = class(TGMAvlTreeNodeBase) public Data: TObject; function GetDataAsIntf: IUnknown; override; function IsDataInstance(const AInstance): Boolean; override; end; TGMAvlObjTree = class(TGMAvlTreeBase, IGMObjCollection) protected FFreeEntries: Boolean; function TreeNodeCreateClass: TGMAvlTreeNodeClass; override; procedure FreeNode(const ANode: TGMAvlTreeNodeBase); override; public constructor Create(const AFreeEntries: Boolean; const AAcceptDuplicates: Boolean = False; const ACompareFunc: TGMIntfCompareFunc = nil; const ARefLifeTime: Boolean = False); reintroduce; overload; function CreateIterator(const AReverse: Boolean = False; const AConcurrentThreadLock: Boolean = False): IGMIterator; override; function Add(const ANewEntry: TObject; const AReplaceIfExists: Boolean = False): TObject; function Find(const AKeyToCompare: IUnknown; out AEntry): Boolean; override; function FindNearest(const AKeyToCompare: IUnknown; out AEntry): Boolean; override; function RemoveByInstance(const AObj: TObject): Boolean; function First: TObject; function Last: TObject; end; TGMAvlObjectTreeIterator = class(TGMAvlTreeIteratorBase) protected procedure AssignOutEntry(out AEntry); override; end; TGMAvlIntfTreeNode = class(TGMAvlTreeNodeBase) public Data: IUnknown; function GetDataAsIntf: IUnknown; override; function IsDataInstance(const AInstance): Boolean; override; end; TGMAvlIntfTree = class(TGMAvlTreeBase, IGMIntfCollection) protected function TreeNodeCreateClass: TGMAvlTreeNodeClass; override; public function CreateIterator(const AReverse: Boolean = False; const AConcurrentThreadLock: Boolean = False): IGMIterator; override; function Add(const ANewEntry: IUnknown; const AReplaceIfExists: Boolean = False): IUnknown; function Find(const AKeyToCompare: IUnknown; out AEntry): Boolean; override; function FindNearest(const AKeyToCompare: IUnknown; out AEntry): Boolean; override; function RemoveByInstance(const AIntf: IUnknown): Boolean; function First: IUnknown; function Last: IUnknown; end; TGMAvlIntfTreeIterator = class(TGMAvlTreeIteratorBase) protected procedure AssignOutEntry(out AEntry); override; end; { --------------------- } { ---- Hash Tables ---- } { --------------------- } // // A multi-level extendable hashing algorithm is implmented here. Offering good performance along with // low memory consumption. Even in case of skewed distributed hash codes. And it scales gracefully // from only a few entries to millions of entries. // const cMaxHashBucketSize = 8; // <- when using sizes greater than 256 extend type TBucketDirIdx accordingly! cMaxHashCodeBits = SizeOf(TGMHashCode) * 8; cMaxHashBitsPerDirLevel = 6; // <- when using more than 8 bits extend type TBucketDirIdx accordingly! cMinHashBitsPerDirLevel = 4; // <- when using more than 8 bits extend type TBucketDirIdx accordingly! type TGMHashTableBase = class; TGMHashEntryBucket = class; IGMHashEntryBucket = interface(IUnknown) ['{E31D8735-3491-4D0C-B421-4FF1458E3ED9}'] function Obj: TGMHashEntryBucket; end; TBucketIdx = Word; // <- if all hash bits are used and still there are duplicates more than cMaxHashBucketSize entries may be put into a single bucket! TGMHashEntryBucket = class(TGMRefCountedObj, IGMHashEntryBucket) protected FEntries: PPointerArray; FCount: TBucketIdx; FCapacity: TBucketIdx; FHashTable: TGMHashTableBase; //FHashBitCount: Byte; function CalcInsertIdx(const AKeyToCompare: IUnknown): TBucketIdx; procedure SetCapacity(const NewCapacity: TBucketIdx); function FindIdxOfKey(const AKeyToCompare: IUnknown; var AIndex: TBucketIdx): Boolean; public constructor Create(const AHashTable: TGMHashTableBase; const ARefLifeTime: Boolean = True); reintroduce; // const AHashBitCount: Byte; destructor Destroy; override; procedure Clear(const AFreeEntries: Boolean); function Obj: TGMHashEntryBucket; function FindKey(const AKeyToCompare: IUnknown; out AEntry): Boolean; function AddPointer(const ANewEntry: Pointer): Boolean; procedure RemoveByIdx(const AIdx: LongInt); function RemoveByKey(const AKeyToCompare: IUnknown): Boolean; function RemovePointer(const AInstance: Pointer): Boolean; end; TGMHashBitMaskDirectory = class; IGMHashBitMaskDirectory = interface(IUnknown) ['{950EF49C-B643-455C-A1B5-AB3C03B8A1F6}'] function Obj: TGMHashBitMaskDirectory; end; TBucketDirIdx = Byte; TGMHashBitMaskDirectory = class(TGMRefCountedObj, IGMHashBitMaskDirectory) protected FHashBitCount: SmallInt; FHashBitOffs: SmallInt; FMaxHashBits: SmallInt; FHashBitMask: TGMHashCode; FHashTable: TGMHashTableBase; FDirEntries: array of IUnknown; FAssignedCount: TBucketDirIdx; procedure ExpandHash(const ADirEntryIdx: LongInt); procedure ReHashBuketEntries(const ABucket: IGMHashEntryBucket); public constructor Create(const AHashTable: TGMHashTableBase; const AMaxHashBits: Byte; const AHashBitOffs: Byte = 0; const ARefLifeTime: Boolean = True); reintroduce; function Obj: TGMHashBitMaskDirectory; function CalcDirEntryIndex(const AHashCode: TGMHashCode): PtrInt; function FindDirEntry(const AHashCode: TGMHashCode): IUnknown; function FindBucket(const AHashCode: TGMHashCode; var ABucket: IGMHashEntryBucket): Boolean; function AddPointer(const AHashCode: TGMHashCode; const ANewEntry: Pointer): Boolean; function RemoveByKey(const AHashCode: TGMHashCode; const AKeyToCompare: IUnknown): Boolean; function RemovePointer(const AInstance: Pointer): Boolean; function FirstEntry: Pointer; function LastEntry: Pointer; end; TGMHashTableBase = class (TGMCollectionBase) protected FFreeEntries: Boolean; FRootDirectory: IGMHashBitMaskDirectory; function BuildHashCode(const AKeyToCompare: IUnknown): TGMHashCode; virtual; function AddPointer(const AHashCode: TGMHashCode; ANewEntry: Pointer): Boolean; function EntryAsIntf(const AEntryPtr: Pointer): IUnknown; virtual; abstract; procedure FreePointer(var AEntryPtr: Pointer); virtual; abstract; procedure AssignOutEntry(const AEntryPtr: Pointer; out AEntry); virtual; abstract; function FirstEntry: Pointer; function LastEntry: Pointer; procedure DoAfterRemove; function RemovePointer(const AInstance: Pointer): Boolean; //procedure SetCompareFunc(const AValue: TGMIntfCompareFunc); override; public function CreateIterator(const AReverse: Boolean = False; const AConcurrentThreadLock: Boolean = False): IGMIterator; override; function Find(const AKeyToCompare: IUnknown; out AEntry): Boolean; override; function FindNearest(const AKeyToCompare: IUnknown; out AEntry): Boolean; override; function RemoveByKey(const AKeyToCompare: IUnknown): Boolean; override; procedure Clear(const ANotify: Boolean = True); override; end; PHashIteratorStackRec = ^THashIteratorStackRec; THashIteratorStackRec = record Directory: IGMHashBitMaskDirectory; DirEntryIdx: LongInt; // <- LongInt NOT TBucketDirIdx to support negative values! end; TGMHashTableIterator = class(TGMIteratorBase) protected FDirStack: array of THashIteratorStackRec; FCurrentBucket: IGMHashEntryBucket; FCurrentBucketEntryIdx: LongInt; // <- LongInt NOT TBucketIdx to support negative values! public procedure Reset; override; function NextEntry(out AEntry): Boolean; override; end; TGMObjHashTable = class(TGMHashTableBase, IGMObjCollection) protected function EntryAsIntf(const AEntryPtr: Pointer): IUnknown; override; procedure FreePointer(var AEntryPtr: Pointer); override; procedure AssignOutEntry(const AEntryPtr: Pointer; out AEntry); override; public constructor Create(const AFreeEntries: Boolean = False; const AAcceptDuplicates: Boolean = True; const ACompareFunc: TGMIntfCompareFunc = nil; const ARefLifeTime: Boolean = False); reintroduce; overload; function Add(const ANewEntry: TObject; const AReplaceIfExists: Boolean = False): TObject; function RemoveByInstance(const AObj: TObject): Boolean; function First: TObject; function Last: TObject; end; TGMIntfHashTable = class(TGMHashTableBase, IGMIntfCollection) protected function EntryAsIntf(const AEntryPtr: Pointer): IUnknown; override; procedure FreePointer(var AEntryPtr: Pointer); override; procedure AssignOutEntry(const AEntryPtr: Pointer; out AEntry); override; public constructor Create(const AAcceptDuplicates: Boolean = True; const ACompareFunc: TGMIntfCompareFunc = nil; const ARefLifeTime: Boolean = False); override; function Add(const ANewEntry: IUnknown; const AReplaceIfExists: Boolean = False): IUnknown; function RemoveByInstance(const AIntf: IUnknown): Boolean; function First: IUnknown; function Last: IUnknown; end; { ----------------------------- } { ---- Generic Collections ---- } { ----------------------------- } {$IFDEF HAS_GENERICS} TGMGenericFinalizeEntryProc<T> = procedure (const Entry: T); TGMGenericCompareFunc<T> = function (const EntryA, EntryB: T): TGMCompareResult; TGMGenericEntryAddRemoveProc<T> = procedure(const Sender: TObject; const Entry: T; const Index: PtrInt) of object; IGMGenericIterator<T> = interface(IUnknown) ['{031CB54D-9BD7-4BA8-A847-A309B4FFD49C}'] function NextEntry(var AEntry: T): Boolean; procedure Reset; end; IGMCreateGenericIterator<T> = interface(IGMGetCount) ['{E57F2FDF-8660-448E-968D-C7248A20EA24}'] function CreateIterator(const AReverse: Boolean = False; const AConcurrentThreadLock: Boolean = False): IGMGenericIterator<T>; end; IGMGenericCollection<T> = interface(IGMCreateGenericIterator<T>) ['{468D1111-7D28-4195-9C44-13065516B0F4}'] function IsEmpty: Boolean; function Add(const ANewEntry: T; const AReplaceIfExists: Boolean = False): T; function Contains(const AEntryToFind: T): Boolean; function Find(const AEntryToFind: T; var AFoundEntry: T): Boolean; function FindNearest(const AEntryToFind: T; var AEntry: T): Boolean; procedure Clear(const ANotify: Boolean = True); function RemoveByKey(const AKeyToCompare: T): Boolean; //function RemoveByInstance(const AInstance: T): Boolean; function CompareEntries(const AEntryA, AEntryB: T): TGMCompareResult; function IsValidIndex(const AIndex: PtrInt): Boolean; function First: T; function Last: T; procedure SetCompareFunc(const Value: TGMGenericCompareFunc<T>); procedure SetOnAfterCountChanged(const Value: TGMCountChangedProc); procedure SetOnAfterAddItem(const Value: TGMGenericEntryAddRemoveProc<T>); procedure SetOnBeforeRemoveItem(const Value: TGMGenericEntryAddRemoveProc<T>); property Count: PtrInt read GetCount; property CompareItemFunc: TGMGenericCompareFunc<T> write SetCompareFunc; property OnAfterCountChanged: TGMCountChangedProc write SetOnAfterCountChanged; property OnAfterAddItem: TGMGenericEntryAddRemoveProc<T> write SetOnAfterAddItem; property OnBeforeRemoveItem: TGMGenericEntryAddRemoveProc<T> write SetOnBeforeRemoveItem; end; TGMGenericIteratorBase<T> = class(TGMRefCountedObj, IGMGenericIterator<T>) protected FCollectionHolder: IUnknown; FReverse: Boolean; //FThreadLock: IUnknown; FThreadLock: RGMCriticalSectionLock; public constructor Create(const ACollection: IUnknown; const AReverse: Boolean = False; const AConcurrentThreadLock: Boolean = False; const ARefLifeTime: Boolean = True); reintroduce; function NextEntry(var AEntry: T): Boolean; virtual; abstract; procedure Reset; virtual; abstract; end; TGMPlainGenericCollection = class(TGMRefCountedObj) // // Class for global symbol access, the compiler doesn't allow to accesse them in generic classes derived from this class ... // public procedure CheckSorted(const ASortedReqired, ASorted: Boolean; const AOperationName: TGMString); function ArrayGrowDelta(const ACurrentCapacity: PtrInt): PtrInt; function MsgStrArrayIndex: TGMString; end; TGMGenericCollectionBase<T> = class(TGMPlainGenericCollection, IGMCriticalSection, IGMClear) protected FCount: PtrInt; //FContainsManagedType: Boolean; FCompareFunc: TGMGenericCompareFunc<T>; FFinalizeEntryProc: TGMGenericFinalizeEntryProc<T>; FCriticalSection: IGMCriticalSection; FAcceptDuplicates: Boolean; FOnAfterCountChanged: TGMCountChangedProc; FOnAfterAddItem: TGMGenericEntryAddRemoveProc<T>; FOnBeforeRemoveItem: TGMGenericEntryAddRemoveProc<T>; procedure NotifyAfterAddItem(const AEntry: T; const AIndex: PtrInt); procedure NotifyBeforeRemoveItem(const AEntry: T; const AIndex: PtrInt); procedure NotifyAfterCountChanged(const AOldCount, ANewCount: PtrInt); public constructor Create(const AAcceptDuplicates: Boolean = True; const ACompareFunc: TGMGenericCompareFunc<T> = nil; const AFinalizeEntryProc: TGMGenericFinalizeEntryProc<T> = nil; const ARefLifeTime: Boolean = True); reintroduce; overload; destructor Destroy; override; procedure Clear(const ANotify: Boolean = False); virtual; abstract; function GetCount: PtrInt; stdcall; procedure SetCompareFunc(const ACompareFunc: TGMGenericCompareFunc<T>); virtual; function IsEmpty: Boolean; virtual; function NullEntry: T; virtual; function CompareEntries(const AEntryA, AEntryB: T): TGMCompareResult; function IsValidIndex(const AIndex: PtrInt): Boolean; procedure SetOnAfterCountChanged(const AValue: TGMCountChangedProc); procedure SetOnAfterAddItem(const AValue: TGMGenericEntryAddRemoveProc<T>); procedure SetOnBeforeRemoveItem(const AValue: TGMGenericEntryAddRemoveProc<T>); property Count: PtrInt read GetCount; property CompareItemFunc: TGMGenericCompareFunc<T> read FCompareFunc write SetCompareFunc; property CriticalSection: IGMCriticalSection read FCriticalSection implements IGMCriticalSection; property OnAfterCountChanged: TGMCountChangedProc read FOnAfterCountChanged write SetOnAfterCountChanged; end; IGMGenericArrayCollection<T> = interface(IGMGenericCollection<T>) ['{448C6101-CC35-49F6-8811-27EEB8F4CC92}'] function GetSorted: Boolean; procedure SetSorted(const AValue: Boolean); procedure Sort; function GetEntry(const AIndex: PtrInt): T; procedure SetEntry(const AIndex: PtrInt; const AValue: T); function IndexOf(const AKeyToCompare: T): PtrInt; function AddIdx(const ANewEntry: T; const AReplaceIfExists: Boolean = False): PtrInt; function Insert(const ANewEntry: T; const AIndex: PtrInt; const AReplaceIfExists: Boolean = False): T; procedure RemoveByIdx(const AIndex: PtrInt; ADelCount: PtrInt = 1); procedure Rotate(ADelta: PtrInt; const AStartIdx: PtrInt = 0); procedure Exchange(const AIndex1, AIndex2: PtrInt); //procedure Move(const ASourceIdx, ADestIdx: PtrInt); procedure Reverse; property Sorted: Boolean read GetSorted write SetSorted; property Entries[AIndex: PtrInt]: T read GetEntry write SetEntry; default; end; TGMGenericArrayCollection<T> = class(TGMGenericCollectionBase<T>, IGMGenericCollection<T>, IGMGenericArrayCollection<T>) protected FEntries: array of T; FCapacity: PtrInt; FSorted: Boolean; function GetSorted: Boolean; procedure SetSorted(const AValue: Boolean); function GetEntry(const AIndex: PtrInt): T; procedure SetEntry(const AIndex: PtrInt; const AValue: T); procedure SetCapacity(ANewCapacity: PtrInt); procedure InternalInsertEntry(const AEntry: T; const AIndex: PtrInt); function CalcInsertIdx(const AEntry: T): PtrInt; function InternalInsert(const AEntry: T; var AIndex: PtrInt; const AReplaceIfExists: Boolean = False): Boolean; procedure QuickSort(L, R: PtrInt); public constructor Create(const AAcceptDuplicates: Boolean = True; const ASorted: Boolean = False; const ACompareFunc: TGMGenericCompareFunc<T> = nil; const AFinalizeEntryProc: TGMGenericFinalizeEntryProc<T> = nil; const ARefLifeTime: Boolean = True); reintroduce; overload; function CreateIterator(const AReverse: Boolean = False; const AConcurrentThreadLock: Boolean = False): IGMGenericIterator<T>; procedure SetCompareFunc(const ACompareFunc: TGMGenericCompareFunc<T>); override; function IndexOfNearest(const AEntryToFind: T): PtrInt; function IsDuplicate(const AEntry: T; var AIndex: PtrInt): Boolean; function IndexOf(const AEntryToFind: T): PtrInt; function Contains(const AEntryToFind: T): Boolean; function Find(const AEntryToFind: T; var AFoundEntry: T): Boolean; function FindNearest(const AEntryToFind: T; var AEntry: T): Boolean; function Add(const ANewEntry: T; const AReplaceIfExists: Boolean = False): T; function AddIdx(const ANewEntry: T; const AReplaceIfExists: Boolean = False): PtrInt; function Insert(const ANewEntry: T; const AIndex: PtrInt; const AReplaceIfExists: Boolean = False): T; procedure RemoveByIdx(const AIndex: PtrInt; ADelCount: PtrInt = 1); function RemoveByKey(const AKeyToCompare: T): Boolean; procedure Clear(const ANotify: Boolean = False); override; function First: T; function Last: T; procedure Rotate(ADelta: PtrInt; const AStartIdx: PtrInt = 0); procedure Exchange(const AIndex1, AIndex2: PtrInt); //procedure Move(const ASourceIdx, ADestIdx: PtrInt); procedure Reverse; procedure Sort; property Entries[AIndex: PtrInt]: T read GetEntry write SetEntry; default; property Capacity: PtrInt read FCapacity {write SetCapacity}; property Sorted: Boolean read FSorted write SetSorted; end; TGMGenericArrayIterator<T> = class(TGMGenericIteratorBase<T>) protected FCollection: IGMGenericArrayCollection<T>; FCurrentIdx: PtrInt; public constructor Create(const ACollection: IGMGenericArrayCollection<T>; const AReverse: Boolean = False; const AConcurrentThreadLock: Boolean = False; const ARefLifeTime: Boolean = True); reintroduce; function NextEntry(var AEntry: T): Boolean; override; procedure Reset; override; end; RGMGenericKeyAndValue<TK, TV> = record Key: TK; Value: TV; end; IGMGenericMap<TK, TV> = interface(IGMCreateGenericIterator<TV>) // RGMGenericKeyAndValue<TK, TV> ['{07AD200B-F250-4E98-B79A-1F533EE084D1}'] function IsEmpty: Boolean; function Add(const AKey: TK; const AValue: TV; const AReplaceIfExists: Boolean = False): TV; function Find(const AKey: TK; var AFoundEntry: TV): Boolean; function FindNearest(const AKeyToCompare: TK; var AFoundEntry: TV): Boolean; procedure Clear(const ANotify: Boolean = True); function Remove(const AKeyToCompare: TK): Boolean; function CompareEntries(const AEntryA, AEntryB: TK): TGMCompareResult; function IsValidIndex(const AIndex: PtrInt): Boolean; function First: TV; function Last: TV; procedure SetCompareFunc(const Value: TGMGenericCompareFunc<TK>); procedure SetOnAfterCountChanged(const Value: TGMCountChangedProc); //procedure SetOnAfterAddItem(const Value: TGMGenericEntryAddRemoveProc<T>); //procedure SetOnBeforeRemoveItem(const Value: TGMGenericEntryAddRemoveProc<T>); property Count: PtrInt read GetCount; property CompareItemFunc: TGMGenericCompareFunc<TK> write SetCompareFunc; property OnAfterCountChanged: TGMCountChangedProc write SetOnAfterCountChanged; //property OnAfterAddItem: TGMGenericEntryAddRemoveProc<T> write SetOnAfterAddItem; //property OnBeforeRemoveItem: TGMGenericEntryAddRemoveProc<T> write SetOnBeforeRemoveItem; end; function GMGenericCompareUsingOperators<T>(const AValueA, AValueB: T): TGMCompareResult; {$ENDIF} { --------------------- } { ---- Integer Map ---- } { --------------------- } type TGMIntegerMap = class; IGMIntegerMap = interface(IUnknown) ['{59388B7E-4264-4128-992D-CCE1BD926721}'] function Obj: TGMIntegerMap; end; PPtrIntArray = ^TPtrIntArray; TPtrIntArray = array [0 .. cMaxIntArraySize-1] of PtrInt; TNotifyIntMapChangeProc = procedure (const Value: PtrInt) of Object; TIndexDecideFunc = function (const Value: PtrInt): Boolean of Object; TGMIntegerMap = class(TGMRefCountedObj, IGMIntegerMap, IGMMapIntegerOnInteger, IGMGetCount, IGMClear) protected FValues: PPtrIntArray; FCount: PtrInt; FCapacity: PtrInt; FChangeNotifyProc: TNotifyIntMapChangeProc; procedure SetCapacity(const ANewCapacity: PtrInt); function GetMappedValue(AIndex: PtrInt): PtrInt; function CalcInsertIdx(const AValue: PtrInt): PtrInt; function GetCount: PtrInt; stdcall; function MapIntegerOnInteger(const AMapKeyValue: PtrInt): PtrInt; virtual; stdcall; procedure IntMapChanged(const AValue: PtrInt); public OnDecideAddValue: TIndexDecideFunc; constructor Create(const ANotifyProc: TNotifyIntMapChangeProc; const ARefLifeTime: Boolean = False); reintroduce; overload; virtual; destructor Destroy; override; function Obj: TGMIntegerMap; procedure Add(const AValue: PtrInt); procedure RemoveByIdx(const AIndex: PtrInt); procedure Remove(const AValue: PtrInt); procedure Toggle(const AValue: PtrInt); procedure Clear(const ANotify: Boolean = True); virtual; procedure AddRange(AValue1, AValue2: PtrInt); procedure SetRange(AValue1, AValue2: PtrInt); function Contains(const AValue: PtrInt): Boolean; function IsEmpty: Boolean; property Values[Index: PtrInt]: PtrInt read GetMappedValue; default; property Count: PtrInt read FCount; end; // // Collection Helpers // function GMCollectionContains(const ACollection, AKeyToCompare: IUnknown): Boolean; function GMCollectionAddAll(const ASrcCollection, ADstCollection: IUnknown; const AClearDstCollection: Boolean = False): PtrInt; function GMCollectionRemoveAll(const AToRemoveCollection, ARemoveFromCollection: IUnknown): PtrInt; procedure GMIntfCollectionLoadAll(const AColletion: IGMCollection; const ASource: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); procedure GMIntfCollectionStoreAll(const AColletion: IGMCollection; const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData = nil); // // Helpers when not using GMCommon and GMIntf units // {$IFDEF STANDALONE_COLLECTIONS} function GMStringJoin(const Value, Separator, Append: TGMString): TGMString; function GMHResultFromWin32(const WinErrorCode: LongWord): HRESULT; function GMQueryInterface(const Obj: IUnknown; const IID: TGUID; out Intf): Boolean; function GMIsInRange(const Value, Min, Max: LongInt): Boolean; function GMBoundedInt(Value, Min, Max: LongInt; const MinBased: Boolean = True): LongInt; procedure GMCheckQueryInterface(const Obj: IUnknown; const IID: TGUID; out Intf; const CallingName: TGMString); function GMHrSucceeded(const AErrorCode: HResult): Boolean; function GMSysErrorMsg(const ErrorCode: LongInt; const Params: array of PGMChar): TGMString; function GMObjAsIntf(const Obj: TObject): IUnknown; procedure GMCheckIntRange(const ValueName: TGMString; const Value, MinValue, MaxValue: LongInt; const Obj: TObject; const CallingName: TGMString); function Min(A, B: LongInt): LongInt; function Max(A, B: LongInt): LongInt; {$ENDIF} implementation uses SysUtils {$IFNDEF STANDALONE_COLLECTIONS}, GMCommon{$ENDIF}; // {$IFDEF HAS_GENERICS}, RTTI{$ENDIF} <- IsManaged(T) function //const // // CStrErrNameSep = ' - '; resourcestring RStrCollectionNotSortedFmt = 'Operation "%s" not supported on unsorted collections'; RStrCollectionSortedFmt = 'Operation "%s" not supported on sorted collections'; RStrArrayIndex = 'Array Index'; RStrListCapacityError = 'List capacity (%d) must be smaller than (%d)'; //RStrInvalidRotateDelta = 'Invalid Rotate Delta: %d'; //RStrInvalidRotateStartPos = 'Invalid Rotate start Position: %d'; RStrMapIndex = 'Map Index'; RStrAvlTreeIntegrityViolation = 'AVL-Tree integrity violation'; RStrLeftChildsParentNotUs = 'The parent member of the left child does not point back to us'; RStrLeftChildGreaterThanUs = 'The left childs value is greater than ours, but it should be less or equal'; RStrRightChildsParentNotUs = 'The parent member of the right child does not point back to us'; RStrWeGreaterThanRightChild = 'Our value is greater than that of our right child, but it should be less or equal.'; RStrWrongNodeBalanceFmt = 'Wrong node balance %d it should be %d'; RStrWrongNodeCountFmt = 'Wrong total tree node count %d it should be %d'; RStrNodeParentLinkError = 'AVL-Tree: The parent node is not correctly linked to the child node'; RStrNoNearestInHashFmt = 'FindNearest not supported in class %s because hashing is not order preserving'; {$IFDEF DEBUG} RStrDuplicateHashEntry = 'Duplicate hash entry'; {$ENDIF} {$IFDEF STANDALONE_COLLECTIONS} RStrTheObjIsNil = 'The Object is nil'; RStrMsgOutOfRangeFmt = '%s out of range: %d. The Value must be in Interval [%d, %d]'; {$ENDIF} const cStrCheckSorted = 'CheckSorted'; cStrCheckUnsorted = 'CheckUnsorted'; { ------------------------------------------------------------------ } { ---- Routines needed when not using GMIntf and GMCommon units ---- } { ------------------------------------------------------------------ } {$IFDEF STANDALONE_COLLECTIONS} function GMStringJoin(const Value, Separator, Append: TGMString): TGMString; begin if Append = '' then Result := Value else if Value = '' then Result := Append else Result := Value + Separator + Append; end; function GMHResultFromWin32(const WinErrorCode: LongWord): HRESULT; // // Looks like Borland Windows.HResultFromWin32 is not correct .. // // #define FACILITY_WIN32 7 // #define HRESULT_FROM_WIN32(x) ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) \ // : ((HRESULT) (((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000))) begin Result := HResult(WinErrorCode); if Result > 0 then Result := ((Result and $0000FFFF) or (FACILITY_WIN32 shl 16) or HRESULT($80000000)); end; function GMSysErrorMsg(const ErrorCode: LongInt; const Params: array of PGMChar): TGMString; var ApiCode: DWORD; PParams: Pointer; function BuildSysErrMsg(Flags: DWORD): TGMString; var buffer: PGMChar; Len: DWORD; begin Len := FormatMessage(Flags or FORMAT_MESSAGE_ALLOCATE_BUFFER, nil, DWORD(ErrorCode), 0, PGMChar(@buffer), 0, PParams); if Len = 0 then begin ApiCode := GetLastError; Result := ''; end else begin ApiCode := ERROR_SUCCESS; while (Len > 0) and (buffer[Len - 1] in [#0..#32, '.']) do Dec(Len); SetString(Result, buffer, Len); //if LocalFree(HLOCAL(buffer)) <> 0 then GMTrace('GMSysErrorMsg - LocalFree failed!', tpWarning); end; end; begin if Length(Params) = 0 then PParams := nil else PParams := @Params[Low(Params)]; Result := BuildSysErrMsg(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_ARGUMENT_ARRAY); if (Result = '') and (ApiCode = ERROR_INVALID_PARAMETER) then Result := BuildSysErrMsg(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_ARGUMENT_ARRAY or FORMAT_MESSAGE_IGNORE_INSERTS); end; function GMQueryInterface(const Obj: IUnknown; const IID: TGUID; out Intf): Boolean; begin Result := (Obj <> nil) and (Obj.QueryInterface(IID, Intf) = S_OK); end; function GMHrSucceeded(const AErrorCode: HResult): Boolean; begin Result := AErrorCode and $80000000 = 0; end; function GMIntfName(const AIntf: IUnknown): TGMString; begin Result := ''; end; function GMObjName(const AObj: TObject): TGMString; begin if AObj <> nil then Result := AObj.ClassName else Result := ''; end; procedure GMCheckQueryInterface(const Obj: IUnknown; const IID: TGUID; out Intf; const CallingName: TGMString); var CallerName: TGMString; Hr: HResult; //function LocalBuildCallingName: TGMString; //begin // if CallerName = '' then CallerName := GMStringJoin(BuildCallingName(CallingName, {$I %CurrentRoutine%}), ' - ', // GMFormat('QueryInterface<%s>("%s")', [GMIntfClassName(Obj), GMGuidToString(IID)])); // Result := CallerName; //end; begin //GMCheckPointerAssigned(Pointer(Obj), RStrTheObject, nil, CallerName); if Obj = nil then raise Exception.Create(GMStringJoin(GMStringJoin(GMIntfName(Obj), cStrErrNameSep, CallingName), ': ', RStrTheObjIsNil)); Hr := Obj.QueryInterface(IID, Intf); if not GMHrSucceeded(Hr) then raise Exception.Create(GMStringJoin(GMStringJoin(GMIntfName(Obj), cStrErrNameSep, CallingName), ': ', GMSysErrorMsg(Hr, []))); //if Obj.QueryInterface(IID, Intf) <> S_OK then raise EGMException.IntfError(MsgIntfNotSupported(RStrTheObject, IID), Obj, BuildCallingName(CallingName, {$I %CurrentRoutine%})); end; function GMObjAsIntf(const Obj: TObject): IUnknown; begin if (Obj = nil) or not Obj.GetInterface(IUnknown, Result) then Result := nil; end; function GMBoundedInt(Value, Min, Max: LongInt; const MinBased: Boolean = True): LongInt; begin if Min > Max then if MinBased then Max := Min else Min := Max; if Value < Min then Result := Min else if Value > Max then Result := Max else Result := Value; end; function GMCompareByInstance(const ItemA, ItemB: IUnknown): TGMCompareResult; begin if PtrUInt(ItemA) > PtrUInt(ItemB) then Result := crAGreaterThanB else if PtrUInt(ItemA) = PtrUInt(ItemB) then Result := crAEqualToB else Result := crALessThanB; end; function GMIsInRange(const Value, Min, Max: LongInt): Boolean; begin Result := (Value >= Min) and (Value <= Max); end; procedure GMCheckIntRange(const ValueName: TGMString; const Value, MinValue, MaxValue: LongInt; const Obj: TObject; const CallingName: TGMString); begin if not GMIsInRange(Value, MinValue, MaxValue) then raise Exception.Create(GMStringJoin(GMStringJoin(GMObjName(Obj), cStrErrNameSep, CallingName), ': ', GMFormat(RStrMsgOutOfRangeFmt, [ValueName, Value, MinValue, MaxValue]))); end; function Min(A, B: LongInt): LongInt; begin if A < B then Result := A else Result := B; end; function Max(A, B: LongInt): LongInt; begin if A > B then Result := A else Result := B; end; {$ENDIF} { ------------------------- } { ---- Global Routines ---- } { ------------------------- } function GrowDelta(const ACurrentCapacity: PtrInt): PtrInt; begin Result := GMBoundedInt(ACurrentCapacity div cGrowDeltaDiv, 1, cGrowDeltaMax); end; function GMCollectionContains(const ACollection, AKeyToCompare: IUnknown): Boolean; var PICollection: IGMCollection; Entry: IUnknown; begin if not GMQueryInterface(ACollection, IGMCollection, PICollection) then Result := False else Result := PICollection.Find(AKeyToCompare, Entry); end; function GMCollectionAddAll(const ASrcCollection, ADstCollection: IUnknown; const AClearDstCollection: Boolean = False): PtrInt; var IntfSrc, IntfDst: IGMIntfCollection; it: IGMIterator; UnkEntry: IUnknown; ObjSrc, ObjDst: IGMObjCollection; ObjEntry: TObject; begin Result := 0; if GMQueryInterface(ASrcCollection, IGMIntfCollection, IntfSrc) and GMQueryInterface(ADstCollection, IGMIntfCollection, IntfDst) then begin if AClearDstCollection then IntfDst.Clear; it := IntfSrc.CreateIterator; while it.NextEntry(UnkEntry) do begin IntfDst.Add(UnkEntry); Inc(Result); end; end else if GMQueryInterface(ASrcCollection, IGMObjCollection, ObjSrc) and GMQueryInterface(ADstCollection, IGMObjCollection, ObjDst) then begin if AClearDstCollection then ObjDst.Clear; it := ObjSrc.CreateIterator; while it.NextEntry(ObjEntry) do begin ObjDst.Add(ObjEntry); Inc(Result); end; end; end; function GMCollectionRemoveAll(const AToRemoveCollection, ARemoveFromCollection: IUnknown): PtrInt; var toRemove, removeFrom: IGMIntfCollection; it: IGMIterator; unkEntry: IUnknown; begin Result := 0; if GMQueryInterface(AToRemoveCollection, IGMIntfCollection, toRemove) and GMQueryInterface(ARemoveFromCollection, IGMIntfCollection, removeFrom) then begin it := toRemove.CreateIterator; while it.NextEntry(unkEntry) do begin removeFrom.RemoveByKey(unkEntry); Inc(Result); end; end; end; procedure RaiseError(const AMsg: TGMString; const ACaller: TObject = nil; const ACallingName: TGMString = ''); begin {$IFNDEF STANDALONE_COLLECTIONS} raise EGMException.ObjError(AMsg, ACaller, ACallingName); {$ELSE} raise Exception.Create(GMStringJoin(GMStringJoin(GMObjName(ACaller), cStrErrNameSep, ACallingName), ': ', AMsg)); {$ENDIF} end; procedure GMIntfCollectionLoadAll(const AColletion: IGMCollection; const ASource: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData); var it: IGMIterator; entry: IUnknown; loadData: IGMLoadStoreData; begin if AColletion = nil then Exit; it := AColletion.CreateIterator; while it.NextEntry(entry) do if GMQueryInterface(entry, IGMLoadStoreData, loadData) then loadData.LoadData(ASource, ACryptCtrlData); end; procedure GMIntfCollectionStoreAll(const AColletion: IGMCollection; const ADest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData); var it: IGMIterator; entry: IUnknown; storeData: IGMLoadStoreData; begin if AColletion = nil then Exit; it := AColletion.CreateIterator; while it.NextEntry(entry) do if GMQueryInterface(entry, IGMLoadStoreData, storeData) then storeData.StoreData(ADest, ACryptCtrlData); end; { -------------------------- } { ---- TGMRefCountedObj ---- } { -------------------------- } {$IFDEF STANDALONE_COLLECTIONS} constructor TGMRefCountedObj.Create(const ARefLifeTime: Boolean); begin inherited Create; FRefLifeTime := ARefLifeTime; FRefCount := 1; // <- artificial RefCount during construction, avoiding immediate destruction // when local interface variables to this instance are used by other objects during construction. end; procedure TGMRefCountedObj.AfterConstruction; begin Dec(FRefCount); inherited AfterConstruction; end; procedure TGMRefCountedObj.BeforeDestruction; begin inherited BeforeDestruction; Inc(FRefCount); end; //destructor TGMRefCountedObj.Destroy; //begin //inherited Destroy; //if Assigned(vfGMCheckRefCountOnDestroy) then vfGMCheckRefCountOnDestroy(RefCount-1, Self); //end; procedure TGMRefCountedObj.OnFinalRelease; begin Free; end; //function TGMRefCountedObj.CreateCopyQI(const IID: TGUID; out Intf): HResult; //var PIUnknown: IUnknown; PIAssign: IGMAssignFromObj; //begin //PIUnknown := TGMRefCountedObjClass(ClassType).Create(True); //Result := PIUnknown.QueryInterface(IID, Intf); //if (Result = S_OK) and (PIUnknown.QueryInterface(IGMAssignFromObj, PIAssign) = S_OK) then PIAssign.AssignFromObj(Self); //end; //function TGMRefCountedObj.GetClassName: PChar; //var RetVal: TGMString; //begin //RetVal := ClassName; //Result := PChar(RetVal); //end; // //function TGMRefCountedObj.GetClassType: TClass; //begin //Result := ClassType; //end; // //function TGMRefCountedObj.GetInstance: TObject; //begin //Result := Self; //end; // //function TGMRefCountedObj.GetTypeInfo: PTypeInfo; //begin //Result := ClassInfo; //end; function TGMRefCountedObj.QueryInterface({$IFDEF FPC}constref{$ELSE}const{$ENDIF} IID: TGUID; out Intf): HResult; begin if GetInterface(IID, Intf) then Result := S_OK else Result := E_NOINTERFACE; //GMTraceQueryInterface(Self, IID, Result); end; function TGMRefCountedObj._AddRef: LongInt; begin Result := InterlockedIncrement(FRefCount); end; function TGMRefCountedObj._Release: LongInt; begin Result := InterlockedDecrement(FRefCount); if (Result = 0) and RefLifeTime then OnFinalRelease; end; {$ENDIF} { ---------------------------- } { ---- TGMCriticalSection ---- } { ---------------------------- } {$IFDEF STANDALONE_COLLECTIONS} constructor TGMCriticalSection.Create(const ARefLifeTime: Boolean); begin inherited Create(ARefLifeTime); Windows.InitializeCriticalSection(FCriticalSection); end; destructor TGMCriticalSection.Destroy; begin Windows.DeleteCriticalSection(FCriticalSection); inherited Destroy; end; procedure TGMCriticalSection.EnterCriticalSection; begin Windows.EnterCriticalSection(FCriticalSection); end; procedure TGMCriticalSection.LeaveCriticalSection; begin Windows.LeaveCriticalSection(FCriticalSection); end; function TGMCriticalSection.TryEnterCriticalSection: Boolean; begin Result := Windows.TryEnterCriticalSection(FCriticalSection); end; {$ENDIF} { -------------------------------- } { ---- TGMCriticalSectionLock ---- } { -------------------------------- } {$IFDEF STANDALONE_COLLECTIONS} constructor TGMCriticalSectionLock.Create(const ACriticalSection: IUnknown; const ARefLifeTime: Boolean); begin inherited Create(ARefLifeTime); if ACriticalSection = nil then Exit; // <- allow nil! // force ACriticalSection to support IGMCriticalSection interface GMCheckQueryInterface(ACriticalSection, IGMCriticalSection, FCriticalSection, {$I %CurrentRoutine%}); if FCriticalSection <> nil then FCriticalSection.EnterCriticalSection; end; destructor TGMCriticalSectionLock.Destroy; begin if FCriticalSection <> nil then FCriticalSection.LeaveCriticalSection; inherited Destroy; end; {$ENDIF} { --------------------------- } { ---- TGMCollectionBase ---- } { --------------------------- } constructor TGMCollectionBase.Create(const AAcceptDuplicates: Boolean; const ACompareFunc: TGMIntfCompareFunc; const ARefLifeTime: Boolean); begin inherited Create(ARefLifeTime); FCriticalSection := TGMCriticalSection.Create(True); FAcceptDuplicates := AAcceptDuplicates; FCompareFunc := ACompareFunc; end; destructor TGMCollectionBase.Destroy; begin Clear(False); inherited Destroy; end; function TGMCollectionBase.GetCount: PtrInt; begin CriticalSection.EnterCriticalSection; // <- not technically needed, but let modifications finish before somebody else may read the count try Result := FCount; finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMCollectionBase.NotifyAfterCountChanged(const OldCount, NewCount: PtrInt); begin if Assigned(FOnAfterCountChanged) then FOnAfterCountChanged(Self, OldCount, NewCount); end; function TGMCollectionBase.CompareEntries(const EntryA, EntryB: IUnknown): TGMCompareResult; begin if Assigned(FCompareFunc) then Result := FCompareFunc(EntryA, EntryB) else Result := GMCompareByInstance(EntryA, EntryB); end; procedure TGMCollectionBase.SetOnAfterCountChanged(const Value: TGMCountChangedProc); begin FOnAfterCountChanged := Value; end; procedure TGMCollectionBase.SetCompareFunc(const AValue: TGMIntfCompareFunc); begin CriticalSection.EnterCriticalSection; try if Addr(AValue) <> Addr(CompareItemFunc) then begin FCompareFunc := AValue; AfterCompareFuncChanged; end; finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMCollectionBase.AfterCompareFuncChanged; begin // Nothing! end; function TGMCollectionBase.IsEmpty: Boolean; begin CriticalSection.EnterCriticalSection; // <- not technically needed, but let modifications finish before somebody else may read the count try Result := FCount <= 0; finally CriticalSection.LeaveCriticalSection; end; end; function TGMCollectionBase.IsValidIndex(const AIndex: PtrInt): Boolean; begin Result := (AIndex >= 0) and (AIndex < FCount); // GMIsInRange(AIndex, 0, FCount-1); end; { ------------------------- } { ---- TGMIteratorBase ---- } { ------------------------- } constructor TGMIteratorBase.Create(const ACollection: TObject; const AReverse, AConcurrentThreadLock: Boolean; const ARefLifeTime: Boolean); begin inherited Create(ARefLifeTime); FCollection := ACollection; FReverse := AReverse; //if AConcurrentThreadLock then FThreadLock := TGMCriticalSectionLock.Create(GMObjAsIntf(ACollection)); if AConcurrentThreadLock then FThreadLock.Lock(ACollection); Reset; end; { -------------------------------- } { ---- TGMArrayCollectionBase ---- } { -------------------------------- } Constructor TGMArrayCollectionBase.Create(const AAcceptDuplicates: Boolean = True; const ASorted: Boolean = False; const ACompareFunc: TGMIntfCompareFunc = nil; const ARefLifeTime: Boolean = False); begin inherited Create(AAcceptDuplicates, ACompareFunc, ARefLifeTime); FSorted := ASorted; end; function TGMArrayCollectionBase.GetSorted: Boolean; begin Result := FSorted; end; procedure TGMArrayCollectionBase.CheckSorted(const AOperationName: TGMString); begin if not FSorted then RaiseError(GMFormat(RStrCollectionNotSortedFmt, [AOperationName]), Self, cStrCheckSorted); end; procedure TGMArrayCollectionBase.CheckUnsorted(const AOperationName: TGMString); begin if FSorted then RaiseError(GMFormat(RStrCollectionSortedFmt, [AOperationName]), Self, cStrCheckUnsorted); end; function TGMArrayCollectionBase.GetIntfByPosition(const APosition: PtrInt; const AIID: TGUID; out AIntf): HResult; var PIUnkItem: IUnknown; begin CriticalSection.EnterCriticalSection; try Result := GMHResultFromWin32(ERROR_INVALID_INDEX); if not GMIsInRange(APosition, 0, FCount-1) then Exit; //GMCheckIntRange(RStrArrayIndex, APosition, 0, FCount-1, Self, {$I %CurrentRoutine%}); PIUnkItem := EntryAsIntf(APosition); if PIUnkItem <> nil then Result := PIUnkItem.QueryInterface(AIID, AIntf); finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMArrayCollectionBase.SetCapacity(const ANewCapacity: PtrInt); begin CriticalSection.EnterCriticalSection; try if (ANewCapacity < 0) or (ANewCapacity > cMaxPtrArraySize) then RaiseError(GMFormat(RStrListCapacityError, [ANewCapacity, cMaxPtrArraySize]), Self, 'SetCapacity'); if ANewCapacity <> FCapacity then begin ReallocMem(FEntries, ANewCapacity * SizeOf(Pointer)); FCapacity := ANewCapacity; end; finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMArrayCollectionBase.Clear(const ANotify: Boolean); var i, oldCount: PtrInt; begin CriticalSection.EnterCriticalSection; try // Better free in reverse order if ANotify or FFreeEntries then for i:=FCount-1 downto 0 do begin if ANotify then NotifyBeforeRemoveItem(TObject(FEntries^[i]), i); if FFreeEntries then FreePointer(FEntries^[i]); end; oldCount := FCount; SetCapacity(0); FCount := 0; if ANotify then NotifyAfterCountChanged(oldCount, 0); finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMArrayCollectionBase.SetSorted(const AValue: Boolean); begin CriticalSection.EnterCriticalSection; try if AValue <> FSorted then begin FSorted := AValue; if AValue then Sort; end; finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMArrayCollectionBase.AfterCompareFuncChanged; begin if Assigned(FCompareFunc) and Sorted then Sort; end; function TGMArrayCollectionBase.IndexOfPointer(const Ptr: Pointer): PtrInt; begin for Result := 0 to FCount-1 do if FEntries^[Result] = Ptr then Exit; Result := cInvalidItemIdx; end; function TGMArrayCollectionBase.IsDuplicate(const AKeyToCompare: IUnknown; var AIndex: PtrInt): Boolean; var idx: PtrInt; begin Result := False; if not FAcceptDuplicates then begin if Sorted then Result := IsValidIndex(AIndex) and (CompareEntries(EntryAsIntf(AIndex), AKeyToCompare) = crAEqualToB) else for idx:=0 to FCount-1 do if CompareEntries(EntryAsIntf(idx), AKeyToCompare) = crAEqualToB then begin AIndex := idx; Result := True; Break; end; end; end; procedure TGMArrayCollectionBase.InsertPointer(const AInstance: Pointer; const AIndex: PtrInt); begin GMCheckIntRange(RStrArrayIndex, AIndex, 0, FCount, Self, {$I %CurrentRoutine%}); if FCount = FCapacity then SetCapacity(FCapacity + GrowDelta(FCapacity)); if AIndex < FCount then System.Move(FEntries^[AIndex], FEntries^[AIndex + 1], (FCount - AIndex) * SizeOf(Pointer)); FEntries^[AIndex] := AInstance; Inc(FCount); NotifyAfterAddItem(AInstance, AIndex); NotifyAfterCountChanged(FCount-1, FCount); end; {procedure TGMArrayCollectionBase.Pack; var i: PtrInt begin CriticalSection.EnterCriticalSection; try for i := FCount - 1 downto 0 do if FEntries^[i] = nil then Delete(i); finally CriticalSection.LeaveCriticalSection; end; end;} {$IFNDEF STANDALONE_COLLECTIONS} procedure TGMArrayCollectionBase.LoadData(const Source: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData); {$ELSE} procedure TGMArrayCollectionBase.LoadData(const Source: IUnknown; const ACryptCtrlData: PGMCryptCtrlData); {$ENDIF} var i: LongInt; loadStore: IGMLoadStoreData; begin for i:=0 to Count-1 do if (GetIntfByPosition(i, IGMLoadStoreData, loadStore) = S_OK) then loadStore.LoadData(Source, ACryptCtrlData); end; {$IFNDEF STANDALONE_COLLECTIONS} procedure TGMArrayCollectionBase.StoreData(const Dest: IGMValueStorage; const ACryptCtrlData: PGMCryptCtrlData); {$ELSE} procedure TGMArrayCollectionBase.StoreData(const Dest: IUnknown; const ACryptCtrlData: PGMCryptCtrlData); {$ENDIF} var i: LongInt; loadStore: IGMLoadStoreData; begin for i:=0 to Count-1 do if (GetIntfByPosition(i, IGMLoadStoreData, loadStore) = S_OK) then loadStore.StoreData(Dest, ACryptCtrlData); end; procedure TGMArrayCollectionBase.RemoveByIdx(const AIndex: PtrInt; ADelCount: PtrInt); var i: PtrInt; begin CriticalSection.EnterCriticalSection; try GMCheckIntRange(RStrArrayIndex, AIndex, 0, FCount-1, Self, {$I %CurrentRoutine%}); ADelCount := Min(ADelCount, FCount - AIndex); if ADelCount <= 0 then Exit; // <- GMCheckIntRange should never raise with this for i:=AIndex to AIndex + ADelCount-1 do begin NotifyBeforeRemoveItem(FEntries^[i], i); if FFreeEntries then {if FEntries^[i] <> nil then} FreePointer(FEntries^[i]); end; Dec(FCount, ADelCount); if AIndex < FCount then System.Move(FEntries^[AIndex + ADelCount], FEntries^[AIndex], (FCount - AIndex) * SizeOf(Pointer)); if FCapacity - FCount >= GrowDelta(FCapacity) then SetCapacity(FCapacity - GrowDelta(FCapacity)); NotifyAfterCountChanged(FCount + ADelCount, FCount); finally CriticalSection.LeaveCriticalSection; end; end; function TGMArrayCollectionBase.CalcInsertIdx(const AKeyToCompare: IUnknown): PtrInt; function _InsertIdx(L, R: PtrInt): PtrInt; var M: PtrInt; begin //if not Assigned(CompareFunc) then Result := FCount else if L >= R then Result := L else begin M := (L + R) shr 1; case CompareEntries(AKeyToCompare, EntryAsIntf(M)) of crAEqualToB, crALessThanB: Result := _InsertIdx(L, M); crAGreaterThanB: if L = M then Result := R else Result := _InsertIdx(M, R); else {$IFNDEF STANDALONE_COLLECTIONS} raise EGMException.ObjError(RStrInvalidCompareResult, Self, {$I %CurrentRoutine%}); {$ELSE} raise Exception.Create(GMStringJoin(GMStringJoin(GMObjName(Self), cStrErrNameSep, {$I %CurrentRoutine%}), ': ', RStrInvalidCompareResult)); {$ENDIF} end; end; end; begin CriticalSection.EnterCriticalSection; try if not FSorted then Result := FCount else Result := _InsertIdx(0, FCount); finally CriticalSection.LeaveCriticalSection; end; end; function TGMArrayCollectionBase.IndexOf(const AKeyToCompare: IUnknown): PtrInt; begin CriticalSection.EnterCriticalSection; try if not Sorted then begin for Result := 0 to FCount-1 do if CompareEntries(EntryAsIntf(Result), AKeyToCompare) = crAEqualToB then Exit; Result := cInvalidItemIdx; end else begin Result := CalcInsertIdx(AKeyToCompare); if not IsValidIndex(Result) or (CompareEntries(EntryAsIntf(Result), AKeyToCompare) <> crAEqualToB) then Result := cInvalidItemIdx; end; finally CriticalSection.LeaveCriticalSection; end; end; function TGMArrayCollectionBase.IndexOfNearest(const AKeyToCompare: IUnknown): PtrInt; begin CriticalSection.EnterCriticalSection; try CheckSorted({$I %CurrentRoutine%}); Result := GMBoundedInt(CalcInsertIdx(AKeyToCompare), 0, FCount-1); if not IsValidIndex(Result) then Result := cInvalidItemIdx; finally CriticalSection.LeaveCriticalSection; end; end; function TGMArrayCollectionBase.Find(const AKeyToCompare: IUnknown; out AEntry): Boolean; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try idx := IndexOf(AKeyToCompare); Result := IsValidIndex(idx); if Result then Pointer(AEntry) := FEntries^[idx]; finally CriticalSection.LeaveCriticalSection; end; end; function TGMArrayCollectionBase.FindNearest(const AKeyToCompare: IUnknown; out AEntry): Boolean; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try idx := IndexOfNearest(AKeyToCompare); Result := IsValidIndex(idx); if Result then Pointer(AEntry) := FEntries^[idx]; finally CriticalSection.LeaveCriticalSection; end; end; function TGMArrayCollectionBase.RemoveByKey(const AKeyToCompare: IUnknown): Boolean; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try idx := IndexOf(AKeyToCompare); Result := IsValidIndex(idx); if Result then RemoveByIdx(idx); finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMArrayCollectionBase.Reverse; var i: PtrInt; begin CriticalSection.EnterCriticalSection; try if FCount > 1 then for i:=0 to FCount-1 shr 1 do Exchange(i, FCount-1-i); finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMArrayCollectionBase.Rotate(ADelta: PtrInt; const AStartIdx: PtrInt); var buffer: Pointer; bufferSize: PtrInt; begin if FCount < 2 then Exit; CriticalSection.EnterCriticalSection; try CheckUnsorted({$I %CurrentRoutine%}); //if Abs(ADelta) >= FCount - AStartIdx then RaiseError(GMFormat(RStrInvalidRotateDelta, [ADelta]), Self, 'Rotate'); ADelta := ADelta mod FCount; GMCheckIntRange(RStrArrayIndex, AStartIdx, 0, FCount-1, Self, {$I %CurrentRoutine%}); //if AStartIdx >= FCount then RaiseError(GMFormat(RStrInvalidRotateStartPos, [AStartIdx]), Self, 'Rotate'); // 32-Bit Release Version of "Abs" did not change -1 to 1 .. //bufferSize := GMAbsPtrInt(ADelta) * SizeOf(Pointer); bufferSize := Abs(ADelta * SizeOf(Pointer)); //vfGMTRace(GMFormat('bufferSize: %d', [bufferSize]), 'bufferSize'); buffer := GetMem(bufferSize); try if ADelta > 0 then begin //GetMem(buffer, bufferSize); //try System.Move(FEntries^[FCount-ADelta], buffer^, bufferSize); System.Move(FEntries^[AStartIdx], FEntries^[ADelta+AStartIdx], (FCount-ADelta-AStartIdx) * SizeOf(Pointer)); System.Move(buffer^, FEntries^[AStartIdx], bufferSize); //finally // FreeMem(buffer); //end; end; if ADelta < 0 then begin //GetMem(buffer, bufferSize); //try System.Move(FEntries^[AStartIdx], buffer^, bufferSize); System.Move(FEntries^[-ADelta+AStartIdx], FEntries^[AStartIdx], (FCount+ADelta-AStartIdx) * SizeOf(Pointer)); System.Move(buffer^, FEntries^[FCount+ADelta], bufferSize); //finally // FreeMem(buffer); //end; end; finally FreeMem(buffer); end; finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMArrayCollectionBase.Exchange(const AIndex1, AIndex2: PtrInt); var tmpItem: Pointer; begin CriticalSection.EnterCriticalSection; try if AIndex1 <> AIndex2 then begin CheckUnsorted({$I %CurrentRoutine%}); GMCheckIntRange(RStrArrayIndex, AIndex1, 0, FCount-1, Self, {$I %CurrentRoutine%}); GMCheckIntRange(RStrArrayIndex, AIndex2, 0, FCount-1, Self, {$I %CurrentRoutine%}); tmpItem := FEntries^[AIndex1]; FEntries^[AIndex1] := FEntries^[AIndex2]; FEntries^[AIndex2] := tmpItem; end; finally CriticalSection.LeaveCriticalSection; end; end; //procedure TGMArrayCollectionBase.Move(const ASourceIdx, ADestIdx: PtrInt); //var tmpItem: Pointer; //begin // CriticalSection.EnterCriticalSection; // try // if ASourceIdx <> ADestIdx then // begin // CheckUnsorted; // GMCheckIntRange(RStrArrayIndex, ASourceIdx, 0, FCount-1, Self, {$I %CurrentRoutine%}); // GMCheckIntRange(RStrArrayIndex, ADestIdx, 0, FCount-1, Self, {$I %CurrentRoutine%}); // // tmpItem := FEntries^[ASourceIdx]; // // if ASourceIdx < ADestIdx then // System.Move(FEntries^[ASourceIdx+1], FEntries^[ASourceIdx], (ADestIdx - ASourceIdx) * SizeOf(Pointer)) // else // System.Move(FEntries^[ADestIdx], FEntries^[ADestIdx+1], (ASourceIdx - ADestIdx) * SizeOf(Pointer)); // // FEntries^[ADestIdx] := tmpItem; // end; // finally // CriticalSection.LeaveCriticalSection; // end; //end; procedure TGMArrayCollectionBase.Sort; procedure QuickSort(L, R: PtrInt); var i, j, m: PtrInt; P: Pointer; begin i := L; j := R; m := (L + R) shr 1; while i <= j do begin while CompareEntries(EntryAsIntf(i), EntryAsIntf(m)) = crALessThanB do Inc(i); while CompareEntries(EntryAsIntf(j), EntryAsIntf(m)) = crAGreaterThanB do Dec(j); if i <= j then begin if i <> j then // and CompareFunc(EntryAsIntf(i), EntryAsIntf(j)) <> crAEqualToB begin P := FEntries^[i]; FEntries^[i] := FEntries^[j]; FEntries^[j] := P; // <- exchange i <-> j if m = i then m := j else if m = j then m := i; // <- if [m] has been exchanged update m end; Inc(i); Dec(j); end; end; if L < j then QuickSort(L, j); if i < R then QuickSort(i, R); end; begin CriticalSection.EnterCriticalSection; try if (FEntries <> nil) and (FCount > 0) then QuickSort(0, FCount-1); finally CriticalSection.LeaveCriticalSection; end; end; { ---------------------------------------- } { ---- TGMArrayCollectionIteratorBase ---- } { ---------------------------------------- } function TGMArrayCollectionIteratorBase.NextEntry(out AEntry): Boolean; begin Result := (FCollection as TGMCollectionBase).IsValidIndex(FCurrentIdx); if Result then begin AssignOutEntry(AEntry); if FReverse then Dec(FCurrentIdx) else Inc(FCurrentIdx); end; end; procedure TGMArrayCollectionIteratorBase.Reset; begin if FReverse then FCurrentIdx := (FCollection as TGMCollectionBase).Count-1 else FCurrentIdx := 0; end; { ------------------------------- } { ---- TGMObjArrayCollection ---- } { ------------------------------- } Constructor TGMObjArrayCollection.Create(const AFreeEntries: Boolean = False; const AAcceptDuplicates: Boolean = True; const ASorted: Boolean = False; const ACompareFunc: TGMIntfCompareFunc = nil; const ARefLifeTime: Boolean = False); begin inherited Create(AAcceptDuplicates, ASorted, ACompareFunc, ARefLifeTime); FFreeEntries := AFreeEntries; end; function TGMObjArrayCollection.CreateIterator(const AReverse, AConcurrentThreadLock: Boolean): IGMIterator; begin Result := TGMObjectCollectionIterator.Create(Self, AConcurrentThreadLock, AReverse, True); end; function TGMObjArrayCollection.EntryAsIntf(const AIndex: PtrInt): IUnknown; begin Result := GMObjAsIntf(FEntries^[AIndex]); end; procedure TGMObjArrayCollection.FreePointer(var AItem: Pointer); begin GMFreeAndNil(TObject(AItem)); end; procedure TGMObjArrayCollection.SetOnAfterAddItem(const AValue: TGMObjItemAddRemoveProc); begin OnAfterAddItem := AValue; end; procedure TGMObjArrayCollection.NotifyAfterAddItem(const AItem: Pointer; const AIndex: PtrInt); begin if Assigned(OnAfterAddItem) then OnAfterAddItem(Self, TObject(AItem), AIndex); end; procedure TGMObjArrayCollection.SetOnBeforeRemoveItem(const Value: TGMObjItemAddRemoveProc); begin OnBeforeRemoveItem := Value; end; procedure TGMObjArrayCollection.NotifyBeforeRemoveItem(const AItem: Pointer; const AIndex: PtrInt); begin if Assigned(OnBeforeRemoveItem) then OnBeforeRemoveItem(Self, TObject(AItem), AIndex); end; function TGMObjArrayCollection.InternalInsert(const AObj: TObject; var AIndex: PtrInt; const AReplaceIfExists: Boolean): Boolean; var doInsert: Boolean; objIntf: IUnknown; begin // prevent GMObjAsIntf call when FAcceptDuplicates = true so objects without IUnknown can be added doInsert := FAcceptDuplicates; if not doInsert then begin objIntf := GMObjAsIntf(AObj); doInsert := not IsDuplicate(objIntf, AIndex); objIntf := nil; end; Result := doInsert or AReplaceIfExists; if doInsert then try InsertPointer(AObj, AIndex); except if FreeEntries then AObj.Free; raise; end else if AReplaceIfExists then begin if FreeEntries then FreePointer(FEntries^[AIndex]); FEntries^[AIndex] := AObj; end else if FreeEntries then AObj.Free; end; function TGMObjArrayCollection.ObjInsertIdx(const AObj: TObject): PtrInt; begin // CalcInsertIdx forces the added object to have the IUnknown interface, but this is only needed when sorted. // Allow Interface-less objects to be added when not sorted if not FSorted then Result := FCount else Result := CalcInsertIdx(GMObjAsIntf(AObj)); end; function TGMObjArrayCollection.Add(const AObj: TObject; const AReplaceIfExists: Boolean): TObject; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try idx := ObjInsertIdx(AObj); InternalInsert(AObj, idx, AReplaceIfExists); //if not InternalInsert(AObj, idx, AReplaceIfExists) and FreeEntries then AObj.Free; Result := FEntries^[idx]; finally CriticalSection.LeaveCriticalSection; end; end; function TGMObjArrayCollection.AddIdx(const AObj: TObject; const AReplaceIfExists: Boolean): PtrInt; begin CriticalSection.EnterCriticalSection; try // CalcInsertIdx forces the added AObj to have IUnknown, only mandatory when sorted. Allow normal objects to be added when not sorted Result := ObjInsertIdx(AObj); InternalInsert(AObj, Result, AReplaceIfExists); //if not InternalInsert(AObj, Result, AReplaceIfExists) and FreeEntries then AObj.Free; finally CriticalSection.LeaveCriticalSection; end; end; function TGMObjArrayCollection.Insert(const AObj: TObject; const AIndex: PtrInt; const AReplaceIfExists: Boolean): TObject; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try CheckUnsorted({$I %CurrentRoutine%}); //if FSorted then Result := Add(AObj, AReplaceIfExists) else // begin idx := AIndex; InternalInsert(AObj, idx, AReplaceIfExists); //if not InternalInsert(AObj, idx, AReplaceIfExists) and FreeEntries then AObj.Free; Result := FEntries^[idx]; //end; finally CriticalSection.LeaveCriticalSection; end; end; function TGMObjArrayCollection.GetItem(const AIndex: PtrInt): TObject; begin CriticalSection.EnterCriticalSection; try GMCheckIntRange(RStrArrayIndex, AIndex, 0, FCount-1, Self, {$I %CurrentRoutine%}); Result := FEntries^[AIndex]; finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMObjArrayCollection.SetItem(const AIndex: PtrInt; const AValue: TObject); begin CriticalSection.EnterCriticalSection; try GMCheckIntRange(RStrArrayIndex, AIndex, 0, FCount-1, Self, {$I %CurrentRoutine%}); if FreeEntries then FreePointer(FEntries^[AIndex]); FEntries^[AIndex] := AValue; finally CriticalSection.LeaveCriticalSection; end; end; function TGMObjArrayCollection.First: TObject; begin CriticalSection.EnterCriticalSection; try if FCount = 0 then Result := nil else Result := GetItem(0); finally CriticalSection.LeaveCriticalSection; end; end; function TGMObjArrayCollection.Last: TObject; begin CriticalSection.EnterCriticalSection; try if FCount = 0 then Result := nil else Result := GetItem(FCount - 1); finally CriticalSection.LeaveCriticalSection; end; end; function TGMObjArrayCollection.IndexOfObj(const AObj: TObject): PtrInt; begin CriticalSection.EnterCriticalSection; try Result := IndexOfPointer(AObj); finally CriticalSection.LeaveCriticalSection; end; end; function TGMObjArrayCollection.RemoveByInstance(const AObj: TObject): Boolean; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try idx := IndexOfObj(AObj); Result := IsValidIndex(idx); if Result then RemoveByIdx(idx); finally CriticalSection.LeaveCriticalSection; end; end; {function TGMObjArrayCollection.FindObj(const Obj: TObject; var Index: LongInt): Boolean; begin CriticalSection.EnterCriticalSection; try Index := IndexOfObj(Obj); Result := IsValidIndex(Index); finally CriticalSection.LeaveCriticalSection; end; end;} { ------------------------------------- } { ---- TGMObjectCollectionIterator ---- } { ------------------------------------- } procedure TGMObjectCollectionIterator.AssignOutEntry(out AEntry); begin TObject(AEntry) := (FCollection as TGMObjArrayCollection)[FCurrentIdx]; end; { -------------------------------- } { ---- TGMIntfArrayCollection ---- } { -------------------------------- } Constructor TGMIntfArrayCollection.Create(const AAcceptDuplicates: Boolean = True; const ASorted: Boolean = False; const ACompareFunc: TGMIntfCompareFunc = nil; const ARefLifeTime: Boolean = True); begin inherited Create(AAcceptDuplicates, ASorted, ACompareFunc, ARefLifeTime); FFreeEntries := True; end; function TGMIntfArrayCollection.CreateIterator(const AReverse, AConcurrentThreadLock: Boolean): IGMIterator; begin Result := TGMIntfArrayCollectionIterator.Create(Self, AConcurrentThreadLock, AReverse, True); end; function TGMIntfArrayCollection.EntryAsIntf(const AIndex: PtrInt): IUnknown; begin Result := IUnknown(FEntries^[AIndex]); end; procedure TGMIntfArrayCollection.FreePointer(var AItem: Pointer); begin //if AItem <> nil then IUnknown(AItem)._Relea<<<<<<<se; if AItem <> nil then IUnknown(AItem) := nil; // <- does a IUnknown(AItem)._Release end; procedure TGMIntfArrayCollection.SetOnAfterAddItem(const AValue: TGMIntfItemAddRemoveProc); begin OnAfterAddItem := AValue; end; procedure TGMIntfArrayCollection.NotifyAfterAddItem(const AItem: Pointer; const AIndex: PtrInt); begin if Assigned(OnAfterAddItem) then OnAfterAddItem(Self, IUnknown(AItem), AIndex); end; procedure TGMIntfArrayCollection.SetOnBeforeRemoveItem(const Value: TGMIntfItemAddRemoveProc); begin OnBeforeRemoveItem := Value; end; procedure TGMIntfArrayCollection.NotifyBeforeRemoveItem(const AItem: Pointer; const AIndex: PtrInt); begin if Assigned(OnBeforeRemoveItem) then OnBeforeRemoveItem(Self, IUnknown(AItem), AIndex); end; function TGMIntfArrayCollection.InternalInsert(const AIntf: IUnknown; var AIndex: PtrInt; const AReplaceIfExists: Boolean = False): Boolean; var unk: IUnknown; doInsert: Boolean; begin GMQueryInterface(AIntf, IUnknown, unk); // <- Always add the real IUnknown representation for proper object identifications and iterator return values doInsert := FAcceptDuplicates or not IsDuplicate(unk, AIndex); Result := doInsert or AReplaceIfExists; if doInsert then begin InsertPointer(Pointer(unk), AIndex); unk._AddRef; end else if AReplaceIfExists then begin FreePointer(FEntries^[AIndex]); FEntries^[AIndex] := Pointer(unk); unk._AddRef; end; //else // FreePointer(AIntf); end; function TGMIntfArrayCollection.AddIdx(const AIntf: IUnknown; const AReplaceIfExists: Boolean): PtrInt; var itemHolder: IUnknown; begin CriticalSection.EnterCriticalSection; try // Without an additional reference stack cleanup after return of CalcInsertIdx may free a newly // created object (with refcount = 0) and the call to InternalInsert would cause an access violation // And itemHolder will cleanup a refcount = 0 AIntf in case of exceptions itemHolder := AIntf; Result := CalcInsertIdx(AIntf); InternalInsert(AIntf, Result, AReplaceIfExists); // if not InternalInsert(AIntf, Result, AReplaceIfExists) then Result := cInvalidItemIdx; finally CriticalSection.LeaveCriticalSection; end; end; function TGMIntfArrayCollection.Add(const AIntf: IUnknown; const AReplaceIfExists: Boolean): IUnknown; var itemHolder: IUnknown; idx: PtrInt; begin CriticalSection.EnterCriticalSection; try // Without an additional reference stack cleanup after return of CalcInsertIdx may free a newly // created object (with refcount = 0) and the call to InternalInsert would cause an access violation // And itemHolder will cleanup a refcount = 0 AIntf in case of exceptions itemHolder := AIntf; idx := CalcInsertIdx(AIntf); InternalInsert(AIntf, idx, AReplaceIfExists); // if (FCount > 0) and (idx = FCount) then Dec(idx); // <- Dupliacte, not inserted! Result := IUnknown(FEntries^[idx]); // if InternalInsert(AIntf, idx, AReplaceIfExists) then Result := IUnknown(FEntries^[idx]) else Result := AIntf; finally CriticalSection.LeaveCriticalSection; end; end; function TGMIntfArrayCollection.Insert(const AIntf: IUnknown; const AIndex: PtrInt; const AReplaceIfExists: Boolean): IUnknown; var itemHolder: IUnknown; idx: PtrInt; begin // itemHolder will cleanup a refcount = 0 AIntf in case of exceptions CriticalSection.EnterCriticalSection; try CheckUnsorted('Insert'); itemHolder := AIntf; //if FSorted then Result := Add(AIntf, AReplaceIfExists) else // begin idx := AIndex; InternalInsert(AIntf, idx, AReplaceIfExists); Result := IUnknown(FEntries^[idx]); // if InternalInsert(AIntf, idx, AReplaceIfExists) then Result := AIntf; // else Result := nil //end; finally CriticalSection.LeaveCriticalSection; end; end; function TGMIntfArrayCollection.Find(const AKeyToCompare: IUnknown; out AEntry): Boolean; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try idx := IndexOf(AKeyToCompare); Result := IsValidIndex(idx); if Result then IUnknown(AEntry) := IUnknown(FEntries^[idx]); finally CriticalSection.LeaveCriticalSection; end; end; function TGMIntfArrayCollection.FindNearest(const AKeyToCompare: IUnknown; out AEntry): Boolean; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try idx := IndexOfNearest(AKeyToCompare); Result := IsValidIndex(idx); if Result then IUnknown(AEntry) := IUnknown(FEntries^[idx]); finally CriticalSection.LeaveCriticalSection; end; end; {function TGMIntfArrayCollection.Remove(const Item: IUnknown): LongInt; begin CriticalSection.EnterCriticalSection; try Result := IndexOfObj(Item); if Result <> cInvalidItemIdx then Delete(Result); finally CriticalSection.LeaveCriticalSection; end; end;} function TGMIntfArrayCollection.GetItem(const Index: PtrInt): IUnknown; begin CriticalSection.EnterCriticalSection; try GMCheckIntRange(RStrArrayIndex, Index, 0, FCount-1, Self, {$I %CurrentRoutine%}); Result := IUnknown(FEntries^[Index]); finally CriticalSection.LeaveCriticalSection; end; end; function TGMIntfArrayCollection.First: IUnknown; begin CriticalSection.EnterCriticalSection; try if FCount = 0 then Result := nil else Result := GetItem(0); finally CriticalSection.LeaveCriticalSection; end; end; function TGMIntfArrayCollection.Last: IUnknown; begin CriticalSection.EnterCriticalSection; try if FCount = 0 then Result := nil else Result := GetItem(FCount - 1); finally CriticalSection.LeaveCriticalSection; end; end; function TGMIntfArrayCollection.IndexOfObj(const AIntf: IUnknown): PtrInt; var unk: IUnknown; begin CriticalSection.EnterCriticalSection; try if GMQueryInterface(AIntf, IUnknown, unk) then // <- Use the real IUnknown representation for proper object identification! Result := IndexOfPointer(Pointer(unk)) else Result := cInvalidItemIdx; finally CriticalSection.LeaveCriticalSection; end; end; function TGMIntfArrayCollection.RemoveByInstance(const AIntf: IUnknown): Boolean; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try idx := IndexOfObj(AIntf); Result := IsValidIndex(idx); if Result then RemoveByIdx(idx); finally CriticalSection.LeaveCriticalSection; end; end; {function TGMIntfArrayCollection.FindObj(const Intf: IUnknown; var Index: LongInt): Boolean; begin CriticalSection.EnterCriticalSection; try Index := IndexOfObj(Intf); Result := IsValidIndex(Index); finally CriticalSection.LeaveCriticalSection; end; end;} { ---------------------------------------- } { ---- TGMIntfArrayCollectionIterator ---- } { ---------------------------------------- } procedure TGMIntfArrayCollectionIterator.AssignOutEntry(out AEntry); begin IUnknown(AEntry) := (FCollection as TGMIntfArrayCollection)[FCurrentIdx]; end; { ---------------------------- } { ---- TGMAvlTreeNodeBase ---- } { ---------------------------- } function TGMAvlTreeNodeBase.TreeHeight: LongInt; var LeftHeight, RightHeight: LongInt; begin if Left <> nil then LeftHeight := Left.TreeHeight + 1 else LeftHeight := 0; if Right <> nil then RightHeight := Right.TreeHeight + 1 else RightHeight := 0; Result := Max(LeftHeight, RightHeight); end; function TGMAvlTreeNodeBase.CalcBalance: LongInt; var LeftHeight, RightHeight: LongInt; begin if Left <> nil then LeftHeight := Left.TreeHeight + 1 else LeftHeight := 0; if Right <> nil then RightHeight := Right.TreeHeight + 1 else RightHeight := 0; Result := RightHeight - LeftHeight; end; procedure TGMAvlTreeNodeBase.ResetMembers; begin Parent := nil; Left := nil; Right := nil; Balance := 0; //Data := nil; end; function TGMAvlTreeNodeBase.GetChild(const AIdx: TGMAvlTreeNodeDirection): TGMAvlTreeNodeBase; begin case AIdx of tndLeft: Result := Left; tndRight: Result := Right; else Result := nil; end; end; procedure TGMAvlTreeNodeBase.SetChild(const AIdx: TGMAvlTreeNodeDirection; const AValue: TGMAvlTreeNodeBase); begin case AIdx of tndLeft: Left := AValue; tndRight: Right := AValue; end; end; { ------------------------ } { ---- TGMAvlTreeBase ---- } { ------------------------ } procedure TGMAvlTreeBase.Clear(const ANotify: Boolean); procedure FreeNodeMemory(ANode: TGMAvlTreeNodeBase); begin if ANode <> nil then begin if ANode.Left <> nil then FreeNodeMemory(ANode.Left); if ANode.Right <> nil then FreeNodeMemory(ANode.Right); FreeNode(ANode); end; end; begin if IsEmpty then Exit; FreeNodeMemory(Root); Root := nil; if ANotify then NotifyAfterCountChanged(FCount, 0); FCount := 0; end; function TGMAvlTreeBase.CreateTreeNode: TGMAvlTreeNodeBase; begin Result := TreeNodeCreateClass.Create; end; procedure TGMAvlTreeBase.FreeNode(const ANode: TGMAvlTreeNodeBase); begin if ANode <> nil then ANode.Free; end; function TGMAvlTreeBase.AddNode(const ANode: TGMAvlTreeNodeBase; const AReplaceIfExists: Boolean): Boolean; var unkNodeData: IUnknown; begin CriticalSection.EnterCriticalSection; try ANode.Left := nil; ANode.Right := nil; if Root = nil then begin Root := ANode; ANode.Parent := nil; end else begin unkNodeData := ANode.GetDataAsIntf; ANode.Parent := FindInsertPos(unkNodeData); if ANode.Parent = nil then begin unkNodeData := nil; FreeNode(ANode); Result := False; Exit; end; // <- Is duplicate but FAcceptDuplicates = False case CompareEntries(unkNodeData, ANode.Parent.GetDataAsIntf) of crALessThanB: ANode.Parent.Left := ANode; //crAEqualToB: if FAcceptDuplicates then ANode.Parent.Right := ANode else begin FreeNode(ANode); Exit; end; //crAGreaterThanB: ANode.Parent.Right := ANode; else ANode.Parent.Right := ANode; end; BalanceAfterInsert(ANode); end; Inc(FCount); NotifyAfterCountChanged(FCount-1, FCount); Result := True; // CheckIntegrity; finally CriticalSection.LeaveCriticalSection; end; end; function TGMAvlTreeBase.FirstNode: TGMAvlTreeNodeBase; begin CriticalSection.EnterCriticalSection; try Result := Root; if Result <> nil then while Result.Left <> nil do Result := Result.Left; finally CriticalSection.LeaveCriticalSection; end; end; function TGMAvlTreeBase.LastNode: TGMAvlTreeNodeBase; begin CriticalSection.EnterCriticalSection; try Result := Root; if Result <> nil then while Result.Right <> nil do Result := Result.Right; finally CriticalSection.LeaveCriticalSection; end; end; function OppositeDirection(const ADirection: TGMAvlTreeNodeDirection): TGMAvlTreeNodeDirection; begin Result := TGMAvlTreeNodeDirection((Ord(ADirection) + 1) mod (Ord(High(ADirection)) + 1)); end; function TGMAvlTreeBase.Rotate(const ANode: TGMAvlTreeNodeBase; const ADirection: TGMAvlTreeNodeDirection): TGMAvlTreeNodeBase; //const cBalanceInc: array [TGMAvlTreeNodeDirection] of LongInt = (1, -1); var PrntNode: TGMAvlTreeNodeBase; OppDir, d: TGMAvlTreeNodeDirection; begin Result := ANode; if ANode = nil then Exit; OppDir := OppositeDirection(ADirection); Result := ANode[OppDir]; if Result = nil then Exit; ANode[OppDir] := Result[ADirection]; if Result[ADirection] <> nil then Result[ADirection].Parent := ANode; PrntNode := Anode.Parent; Result[ADirection] := ANode; ANode.Parent := Result; Result.Parent := PrntNode; if PrntNode = nil then Root := Result else for d:=Low(d) to High(d) do if PrntNode[d] = ANode then begin PrntNode[d] := Result; //if Result.Balance <> 0 then Inc(PrntNode.Balance, cBalanceInc[d]); Break; end; //ANode.Balance := cBalanceInc[ADirection] - Result.Balance; //Inc(Result.Balance, cBalanceInc[ADirection]); end; function TGMAvlTreeBase.DoubleRotate(const ANode: TGMAvlTreeNodeBase; const ADirection: TGMAvlTreeNodeDirection): TGMAvlTreeNodeBase; var OppDir: TGMAvlTreeNodeDirection; begin Result := ANode; if ANode = nil then Exit; OppDir := OppositeDirection(ADirection); Rotate(ANode[OppDir], OppDir); Result := Rotate(ANode, ADirection); end; procedure TGMAvlTreeBase.BalanceAfterInsert(const ANode: TGMAvlTreeNodeBase); const cBalanceInc: array [TGMAvlTreeNodeDirection] of LongInt = (-1, 1); var ParentNode, ChildNode: TGMAvlTreeNodeBase; Dir, OppDir: TGMAvlTreeNodeDirection; begin if (ANode = nil) or (ANode.Parent = nil) then Exit; ParentNode := ANode.Parent; if ParentNode.Left = ANode then Dir := tndLeft else if ParentNode.Right = ANode then Dir := tndRight else {$IFNDEF STANDALONE_COLLECTIONS} raise EGMException.ObjError(RStrNodeParentLinkError, ANode, {$I %CurrentRoutine%}); {$ELSE} raise Exception.Create(GMStringJoin(GMStringJoin(GMObjName(ANode), cStrErrNameSep, {$I %CurrentRoutine%}), ': ', RStrNodeParentLinkError)); {$ENDIF} OppDir := OppositeDirection(Dir); Inc(ParentNode.Balance, cBalanceInc[Dir]); if ParentNode.Balance = 0 then Exit; if ParentNode.Balance = cBalanceInc[Dir] then begin BalanceAfterInsert(ParentNode); Exit; end; if ANode.Balance = cBalanceInc[Dir] then begin Rotate(Anode.Parent, OppDir); ANode.Balance := 0; ParentNode.Balance := 0; end else begin ChildNode := ANode[OppDir]; DoubleRotate(Anode.Parent, OppDir); case Dir of tndLeft: if ChildNode.Balance <= 0 then ANode.Balance := 0 else ANode.Balance := -1; tndRight: if ChildNode.Balance >= 0 then ANode.Balance := 0 else ANode.Balance := 1; end; if ChildNode.Balance = cBalanceInc[Dir] then ParentNode.Balance := cBalanceInc[OppDir] else ParentNode.Balance := 0; ChildNode.Balance := 0; end; end; procedure TGMAvlTreeBase.BalanceAfterDelete(const ANode: TGMAvlTreeNodeBase); var NodeParent, RightChild, RightLeftchild, LeftChild, LeftRightChild: TGMAvlTreeNodeBase; begin if (ANode = nil) then Exit; if ((ANode.Balance = 1) or (ANode.Balance = -1)) then Exit; NodeParent := ANode.Parent; if ANode.Balance = 0 then begin // Treeheight has decreased by one if NodeParent <> nil then begin if NodeParent.Left = ANode then Inc(NodeParent.Balance) else Dec(NodeParent.Balance); BalanceAfterDelete(NodeParent); end; Exit; end; if ANode.Balance = 2 then begin // Node is overweighted to the right RightChild := ANode.Right; if RightChild.Balance >= 0 then begin // RightChild.Balance is 0 or -1 Rotate(ANode, tndLeft); ANode.Balance := 1 - RightChild.Balance; Dec(RightChild.Balance); BalanceAfterDelete(RightChild); end else begin // RightChild.Balance is -1 RightLeftchild := RightChild.Left; DoubleRotate(ANode, tndLeft); if RightLeftchild.Balance <= 0 then ANode.Balance := 0 else ANode.Balance := -1; if RightLeftchild.Balance >= 0 then RightChild.Balance := 0 else RightChild.Balance := 1; RightLeftchild.Balance := 0; BalanceAfterDelete(RightLeftchild); end; end else begin // Node.Balance is -2 // Node is overweighted to the left LeftChild := ANode.Left; if (LeftChild.Balance <= 0) then begin Rotate(ANode, tndRight); ANode.Balance := -1 - LeftChild.Balance; Inc(LeftChild.Balance); BalanceAfterDelete(LeftChild); end else begin // LeftChild.Balance is 1 LeftRightChild := LeftChild.Right; DoubleRotate(ANode, tndRight); if LeftRightChild.Balance >= 0 then ANode.Balance := 0 else ANode.Balance := 1; if LeftRightChild.Balance <= 0 then LeftChild.Balance := 0 else LeftChild.Balance := -1; LeftRightChild.Balance := 0; BalanceAfterDelete(LeftRightChild); end; end; end; procedure TGMAvlTreeBase.DeleteNode(const ANode: TGMAvlTreeNodeBase); var OldParent, OldLeft, OldRight, Successor, OldSuccParent, OldSuccLeft, OldSuccRight: TGMAvlTreeNodeBase; OldBalance: LongInt; begin CriticalSection.EnterCriticalSection; try OldParent := ANode.Parent; OldBalance := ANode.Balance; ANode.Parent := nil; ANode.Balance := 0; if ((ANode.Left = nil) and (ANode.Right = nil)) then begin // Node is Leaf (no children) if OldParent <> nil then begin // Node has parent if OldParent.Left = ANode then begin OldParent.Left := nil; Inc(OldParent.Balance); end // <- Node is left Son of OldParent else begin OldParent.Right := nil; Dec(OldParent.Balance); end; // <- Node is right Son of OldParent BalanceAfterDelete(OldParent); end else Root := nil; // <- Node is the only node in the tree Dec(FCount); FreeNode(ANode); NotifyAfterCountChanged(FCount+1, FCount); Exit; end; if ANode.Right = nil then begin // Left is only son // and because DelNode is AVL, Right has no childrens // replace DelNode with Left OldLeft := ANode.Left; ANode.Left := nil; OldLeft.Parent := OldParent; if (OldParent <> nil) then begin if (OldParent.Left=ANode) then begin OldParent.Left := OldLeft; Inc(OldParent.Balance); end else begin OldParent.Right := OldLeft; Dec(OldParent.Balance); end; BalanceAfterDelete(OldParent); end else begin Root := OldLeft; end; Dec(FCount); FreeNode(ANode); NotifyAfterCountChanged(FCount+1, FCount); Exit; end; if ANode.Left = nil then begin // Right is only son // and because DelNode is AVL, Left has no childrens // replace DelNode with Right OldRight := ANode.Right; ANode.Right := nil; OldRight.Parent := OldParent; if (OldParent <> nil) then begin if (OldParent.Left=ANode) then begin OldParent.Left := OldRight; Inc(OldParent.Balance); end else begin OldParent.Right := OldRight; Dec(OldParent.Balance); end; BalanceAfterDelete(OldParent); end else begin Root := OldRight; end; Dec(FCount); FreeNode(ANode); NotifyAfterCountChanged(FCount+1, FCount); Exit; end; // DelNode has both: Left and Right // Replace ANode with symmetric Successor Successor := SuccessorNode(ANode); OldLeft := ANode.Left; OldRight := ANode.Right; OldSuccParent := Successor.Parent; OldSuccLeft := Successor.Left; OldSuccRight := Successor.Right; ANode.Balance := Successor.Balance; Successor.Balance := OldBalance; if OldSuccParent <> ANode then begin // at least one node between ANode and Successor ANode.Parent := Successor.Parent; if OldSuccParent.Left = Successor then OldSuccParent.Left := ANode else OldSuccParent.Right := ANode; Successor.Right := OldRight; OldRight.Parent := Successor; end else begin // Successor is right son of ANode ANode.Parent := Successor; Successor.Right := ANode; end; Successor.Left := OldLeft; if OldLeft <> nil then OldLeft.Parent := Successor; Successor.Parent := OldParent; ANode.Left := OldSuccLeft; if ANode.Left <> nil then ANode.Left.Parent := ANode; ANode.Right := OldSuccRight; if ANode.Right <> nil then ANode.Right.Parent := ANode; if OldParent <> nil then begin if (OldParent.Left=ANode) then OldParent.Left := Successor else OldParent.Right := Successor; end else Root := Successor; DeleteNode(ANode); // <- recursive call! // CheckIntegrity; finally CriticalSection.LeaveCriticalSection; end; end; function TGMAvlTreeBase.RemoveByKey(const AKeyToCompare: IUnknown): Boolean; var Node: TGMAvlTreeNodeBase; begin CriticalSection.EnterCriticalSection; try Node := FindNode(AKeyToCompare); Result := Node <> nil; if Result then DeleteNode(Node); finally CriticalSection.LeaveCriticalSection; end; end; function TGMAvlTreeBase.RemoveByInstance(const AInstance): Boolean; var node: TGMAvlTreeNodeBase; begin CriticalSection.EnterCriticalSection; try node := FirstNode; Result := False; while node <> nil do begin if node.IsDataInstance(AInstance) then begin DeleteNode(node); Result := True; Break; end; node := SuccessorNode(node); end; finally CriticalSection.LeaveCriticalSection; end; end; function TGMAvlTreeBase.FindNode(const AKeyToCompare: IUnknown): TGMAvlTreeNodeBase; begin CriticalSection.EnterCriticalSection; try Result := Root; while Result <> nil do case CompareEntries(AKeyToCompare, Result.GetDataAsIntf) of crALessThanB: Result := Result.Left; crAEqualToB: Break; crAGreaterThanB: Result := Result.Right; end; finally CriticalSection.LeaveCriticalSection; end; end; function TGMAvlTreeBase.FindNearestNode(const AKeyToCompare: IUnknown): TGMAvlTreeNodeBase; begin CriticalSection.EnterCriticalSection; try Result := Root; while Result <> nil do case CompareEntries(AKeyToCompare, Result.GetDataAsIntf) of crALessThanB: if Result.Left <> nil then Result := Result.Left else Break; crAEqualToB: Break; crAGreaterThanB: if Result.Right <> nil then Result := Result.Right else Break; end; finally CriticalSection.LeaveCriticalSection; end; end; function TGMAvlTreeBase.FindInsertPos(const AKeyToCompare: IUnknown): TGMAvlTreeNodeBase; Label GreaterThanCase; begin CriticalSection.EnterCriticalSection; try Result := Root; while Result <> nil do case CompareEntries(AKeyToCompare, Result.GetDataAsIntf) of crALessThanB: if Result.Left <> nil then Result := Result.Left else Break; crAEqualToB: if FAcceptDuplicates then goto GreaterThanCase else begin Result := nil; Break; end; crAGreaterThanB: GreaterThanCase: if Result.Right <> nil then Result := Result.Right else Break; end; finally CriticalSection.LeaveCriticalSection; end; end; function TGMAvlTreeBase.SuccessorNode(const ANode: TGMAvlTreeNodeBase): TGMAvlTreeNodeBase; begin CriticalSection.EnterCriticalSection; try if ANode = nil then begin Result := nil; Exit; end; Result := ANode.Right; if Result <> nil then begin while Result.Left <> nil do Result := Result.Left; end else begin Result := ANode; while (Result.Parent <> nil) and (Result.Parent.Right = Result) do Result := Result.Parent; Result := Result.Parent; end; finally CriticalSection.LeaveCriticalSection; end; end; function TGMAvlTreeBase.PredecessorNode(const ANode: TGMAvlTreeNodeBase): TGMAvlTreeNodeBase; begin CriticalSection.EnterCriticalSection; try if ANode = nil then begin Result := nil; Exit; end; Result := ANode.Left; if Result <> nil then begin while Result.Right <> nil do Result := Result.Right; end else begin Result := ANode; while (Result.Parent <> nil) and (Result.Parent.Left = Result) do Result := Result.Parent; Result := Result.Parent; end; finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMAvlTreeBase.CheckIntegrity; var checkCount: integer; procedure RaiseIntegrityException(const AMsg: TGMString); begin raise Exception.Create(RStrAvlTreeIntegrityViolation + ': ' + AMsg); end; procedure CheckNode(ANode: TGMAvlTreeNodeBase); var LeftHeight, RightHeight: integer; begin if ANode = nil then Exit; Inc(checkCount); // Check left child if ANode.Left <> nil then begin if ANode.Left.Parent <> ANode then RaiseIntegrityException(RStrLeftChildsParentNotUs); if CompareEntries(ANode.Left.GetDataAsIntf, ANode.GetDataAsIntf) = crAGreaterThanB then RaiseIntegrityException(RStrLeftChildGreaterThanUs); CheckNode(ANode.Left); end; // Check right child if ANode.Right <> nil then begin if ANode.Right.Parent <> ANode then RaiseIntegrityException(RStrRightChildsParentNotUs); if CompareEntries(ANode.GetDataAsIntf, ANode.Right.GetDataAsIntf) = crAGreaterThanB then RaiseIntegrityException(RStrWeGreaterThanRightChild); CheckNode(ANode.Right); end; // Check balance if ANode.Left <> nil then LeftHeight := ANode.Left.TreeHeight + 1 else LeftHeight := 0; if ANode.Right <> nil then RightHeight := ANode.Right.TreeHeight + 1 else RightHeight := 0; if ANode.Balance <> (RightHeight-LeftHeight) then RaiseIntegrityException(GMFormat(RStrWrongNodeBalanceFmt, [ANode.Balance, RightHeight-LeftHeight])); end; begin CriticalSection.EnterCriticalSection; try checkCount := 0; CheckNode(Root); if FCount <> checkCount then RaiseIntegrityException(GMFormat(RStrWrongNodeCountFmt, [FCount, checkCount])); finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMAvlTreeBase.SetCompareFunc(const AValue: TGMIntfCompareFunc); var Nodes: array of TGMAvlTreeNodeBase; Node: TGMAvlTreeNodeBase; i, OldCount: integer; begin CriticalSection.EnterCriticalSection; try if Addr(FCompareFunc) = Addr(AValue) then Exit; if Count = 0 then FCompareFunc := AValue else begin OldCount := Count; SetLength(Nodes, OldCount); Node := FirstNode; i := Low(Nodes); while Node <> nil do begin Nodes[i] := Node; Inc(i); Node := SuccessorNode(Node); end; Root := nil; FCount := 0; FCompareFunc := AValue; for i:=Low(Nodes) to High(Nodes) do AddNode(Nodes[i], False); end; finally CriticalSection.LeaveCriticalSection; end; end; { -------------------------------- } { ---- TGMAvlTreeIteratorBase ---- } { -------------------------------- } function TGMAvlTreeIteratorBase.NextEntry(out AEntry): Boolean; begin Result := FCurrentNode <> nil; if Result then begin AssignOutEntry(AEntry); if FReverse then FCurrentNode := (FCollection as TGMAvlTreeBase).PredecessorNode(FCurrentNode) else FCurrentNode := (FCollection as TGMAvlTreeBase).SuccessorNode(FCurrentNode); end; end; procedure TGMAvlTreeIteratorBase.Reset; begin if FReverse then FCurrentNode := (FCollection as TGMAvlTreeBase).LastNode else FCurrentNode := (FCollection as TGMAvlTreeBase).FirstNode; end; { --------------------------- } { ---- TGMAvlObjTreeNode ---- } { --------------------------- } function TGMAvlObjTreeNode.GetDataAsIntf: IUnknown; begin Result := GMObjAsIntf(Data); end; function TGMAvlObjTreeNode.IsDataInstance(const AInstance): Boolean; begin Result := Data = TObject(AInstance); end; { ----------------------- } { ---- TGMAvlObjTree ---- } { ----------------------- } constructor TGMAvlObjTree.Create(const AFreeEntries: Boolean; const AAcceptDuplicates: Boolean; const ACompareFunc: TGMIntfCompareFunc; const ARefLifeTime: Boolean); begin inherited Create(AAcceptDuplicates, ACompareFunc, ARefLifeTime); FFreeEntries := AFreeEntries; end; function TGMAvlObjTree.TreeNodeCreateClass: TGMAvlTreeNodeClass; begin Result := TGMAvlObjTreeNode; end; function TGMAvlObjTree.CreateIterator(const AReverse, AConcurrentThreadLock: Boolean): IGMIterator; begin Result := TGMAvlObjectTreeIterator.Create(Self, AConcurrentThreadLock, AReverse, True); end; function TGMAvlObjTree.Add(const ANewEntry: TObject; const AReplaceIfExists: Boolean): TObject; var NewNode: TGMAvlTreeNodeBase; begin NewNode := CreateTreeNode; (NewNode as TGMAvlObjTreeNode).Data := ANewEntry; if AddNode(NewNode, AReplaceIfExists) then Result := ANewEntry else Result := nil; end; procedure TGMAvlObjTree.FreeNode(const ANode: TGMAvlTreeNodeBase); begin if FFreeEntries and ((ANode as TGMAvlObjTreeNode).Data <> nil) then TGMAvlObjTreeNode(ANode).Data.Free; //GMFreeAndNil((ANode as TGMAvlObjTreeNode).Data); // .Free; inherited FreeNode(ANode); end; function TGMAvlObjTree.Find(const AKeyToCompare: IUnknown; out AEntry): Boolean; var Node: TGMAvlTreeNodeBase; begin Node := FindNode(AKeyToCompare); Result := (Node is TGMAvlObjTreeNode) and (TGMAvlObjTreeNode(Node).Data <> nil); if Result then TObject(AEntry) := TGMAvlObjTreeNode(Node).Data; end; function TGMAvlObjTree.FindNearest(const AKeyToCompare: IUnknown; out AEntry): Boolean; var Node: TGMAvlTreeNodeBase; begin Node := FindNearestNode(AKeyToCompare); Result := (Node is TGMAvlObjTreeNode) and (TGMAvlObjTreeNode(Node).Data <> nil); if Result then TObject(AEntry) := TGMAvlObjTreeNode(Node).Data; end; function TGMAvlObjTree.RemoveByInstance(const AObj: TObject): Boolean; begin Result := inherited RemoveByInstance(AObj); end; function TGMAvlObjTree.First: TObject; var Node: TGMAvlTreeNodeBase; begin Node := FirstNode; if Node = nil then Result := nil else Result := (Node as TGMAvlObjTreeNode).Data; end; function TGMAvlObjTree.Last: TObject; var Node: TGMAvlTreeNodeBase; begin Node := LastNode; if Node = nil then Result := nil else Result := (Node as TGMAvlObjTreeNode).Data; end; { ---------------------------------- } { ---- TGMAvlObjectTreeIterator ---- } { ---------------------------------- } procedure TGMAvlObjectTreeIterator.AssignOutEntry(out AEntry); begin TObject(AEntry) := (FCurrentNode as TGMAvlObjTreeNode).Data; end; { ---------------------------- } { ---- TGMAvlIntfTreeNode ---- } { ---------------------------- } function TGMAvlIntfTreeNode.GetDataAsIntf: IUnknown; begin Result := Data; end; function TGMAvlIntfTreeNode.IsDataInstance(const AInstance): Boolean; var Unk: IUnknown; begin GMQueryInterface(IUnknown(AInstance), IUnknown, unk); // <- Use the real IUnknown representation for proper object identifications Result := Data = unk; end; { ------------------------ } { ---- TGMAvlIntfTree ---- } { ------------------------ } function TGMAvlIntfTree.TreeNodeCreateClass: TGMAvlTreeNodeClass; begin Result := TGMAvlIntfTreeNode; end; function TGMAvlIntfTree.CreateIterator(const AReverse, AConcurrentThreadLock: Boolean): IGMIterator; begin Result := TGMAvlIntfTreeIterator.Create(Self, AConcurrentThreadLock, AReverse, True); end; function TGMAvlIntfTree.Add(const ANewEntry: IUnknown; const AReplaceIfExists: Boolean): IUnknown; var NewNode: TGMAvlTreeNodeBase; unk: IUnknown; begin GMQueryInterface(ANewEntry, IUnknown, unk); // <- Always add the real IUnknown representation for proper object identifications NewNode := CreateTreeNode; (NewNode as TGMAvlIntfTreeNode).Data := unk; if AddNode(NewNode, AReplaceIfExists) then Result := ANewEntry else Result := nil; end; function TGMAvlIntfTree.Find(const AKeyToCompare: IUnknown; out AEntry): Boolean; var Node: TGMAvlTreeNodeBase; begin Node := FindNode(AKeyToCompare); Result := (Node is TGMAvlIntfTreeNode) and (TGMAvlIntfTreeNode(Node).Data <> nil); if Result then IUnknown(AEntry) := TGMAvlIntfTreeNode(Node).Data; end; function TGMAvlIntfTree.FindNearest(const AKeyToCompare: IUnknown; out AEntry): Boolean; var Node: TGMAvlTreeNodeBase; begin Node := FindNearestNode(AKeyToCompare); Result := (Node is TGMAvlIntfTreeNode) and (TGMAvlIntfTreeNode(Node).Data <> nil); if Result then IUnknown(AEntry) := TGMAvlIntfTreeNode(Node).Data; end; function TGMAvlIntfTree.RemoveByInstance(const AIntf: IUnknown): Boolean; var unk: IUnknown; begin Result := GMQueryInterface(AIntf, IUnknown, unk) and inherited RemoveByInstance(unk); // <- use real Iunknown representation for proper object identification end; function TGMAvlIntfTree.First: IUnknown; var Node: TGMAvlTreeNodeBase; begin Node := FirstNode; if Node = nil then Result := nil else Result := (Node as TGMAvlIntfTreeNode).Data; end; function TGMAvlIntfTree.Last: IUnknown; var Node: TGMAvlTreeNodeBase; begin Node := LastNode; if Node = nil then Result := nil else Result := (Node as TGMAvlIntfTreeNode).Data; end; { -------------------------------- } { ---- TGMAvlIntfTreeIterator ---- } { -------------------------------- } procedure TGMAvlIntfTreeIterator.AssignOutEntry(out AEntry); begin IUnknown(AEntry) := (FCurrentNode as TGMAvlIntfTreeNode).Data; end; { ---------------------------- } { ---- TGMHashEntryBucket ---- } { ---------------------------- } constructor TGMHashEntryBucket.Create(const AHashTable: TGMHashTableBase; const ARefLifeTime: Boolean); // ; const AHashBitCount: Byte; begin inherited Create(ARefLifeTime); FHashTable := AHashTable; //FHashBitCount := AHashBitCount; end; destructor TGMHashEntryBucket.Destroy; begin Clear(True); SetCapacity(0); inherited; end; procedure TGMHashEntryBucket.Clear(const AFreeEntries: Boolean); var i: LongInt; begin if AFreeEntries and FHashTable.FFreeEntries then for i:=0 to FCount-1 do FHashTable.FreePointer(FEntries^[i]); FCount := 0; end; function TGMHashEntryBucket.Obj: TGMHashEntryBucket; begin Result := Self; end; procedure TGMHashEntryBucket.SetCapacity(const NewCapacity: TBucketIdx); begin {TODO: Bessere Fehlermeldung} //if (NewCapacity < 0) or (NewCapacity > cMaxPtrArraySize) then raise EGMException.ObjError(GMFormat(RStrListCapacityError, [NewCapacity, cMaxPtrArraySize]), Self, 'SetCapacity'); if NewCapacity <> FCapacity then begin ReallocMem(FEntries, NewCapacity * SizeOf(Pointer)); FCapacity := NewCapacity; end; end; function TGMHashEntryBucket.CalcInsertIdx(const AKeyToCompare: IUnknown): TBucketIdx; function _InsertIdx(L, R: TBucketIdx): TBucketIdx; var M: LongInt; begin if L >= R then Result := L else begin M := (L + R) shr 1; case FHashTable.CompareEntries(AKeyToCompare, FHashTable.EntryAsIntf(FEntries^[M])) of crAEqualToB, crALessThanB: Result := _InsertIdx(L, M); crAGreaterThanB: if L = M then Result := R else Result := _InsertIdx(M, R); else {$IFNDEF STANDALONE_COLLECTIONS} raise EGMException.ObjError(RStrInvalidCompareResult, Self, {$I %CurrentRoutine%}); {$ELSE} raise Exception.Create(GMStringJoin(GMStringJoin(GMObjName(Self), cStrErrNameSep, {$I %CurrentRoutine%}), ': ', RStrInvalidCompareResult)); {$ENDIF} end; end; end; begin Result := _InsertIdx(0, FCount); end; function TGMHashEntryBucket.FindIdxOfKey(const AKeyToCompare: IUnknown; var AIndex: TBucketIdx): Boolean; begin AIndex := CalcInsertIdx(AKeyToCompare); Result := GMIsInRange(AIndex, 0, FCount-1) and (FHashTable.CompareEntries(FHashTable.EntryAsIntf(FEntries^[AIndex]), AKeyToCompare) = crAEqualToB); end; function TGMHashEntryBucket.FindKey(const AKeyToCompare: IUnknown; out AEntry): Boolean; var idx: TBucketIdx; begin Result := FindIdxOfKey(AKeyToCompare, idx); if Result then FHashTable.AssignOutEntry(FEntries^[idx], AEntry); end; function TGMHashEntryBucket.AddPointer(const ANewEntry: Pointer): Boolean; var idx: LongInt; EntryIntf: IUnknown; begin EntryIntf := FHashTable.EntryAsIntf(ANewEntry); idx := CalcInsertIdx(EntryIntf); Result := FHashTable.FAcceptDuplicates or (idx = FCount) or (FHashTable.CompareEntries(EntryIntf, FHashTable.EntryAsIntf(FEntries^[idx])) <> crAEqualToB); if not Result then Exit; if FCount = FCapacity then SetCapacity(FCapacity + GrowDelta(FCapacity)); if idx < FCount then System.Move(FEntries^[idx], FEntries^[idx + 1], (FCount - idx) * SizeOf(Pointer)); FEntries^[idx] := ANewEntry; Inc(FCount); end; procedure TGMHashEntryBucket.RemoveByIdx(const AIdx: LongInt); begin //NotifyBeforeRemoveItem(FEntries^[i], i); if FHashTable.FFreeEntries then FHashTable.FreePointer(FEntries^[AIdx]); Dec(FCount); if AIdx < FCount then System.Move(FEntries^[AIdx + 1], FEntries^[AIdx], (FCount - AIdx) * SizeOf(Pointer)); if FCapacity - FCount >= GrowDelta(FCapacity) then SetCapacity(FCapacity - GrowDelta(FCapacity)); end; function TGMHashEntryBucket.RemoveByKey(const AKeyToCompare: IUnknown): Boolean; var idx: TBucketIdx; begin Result := FindIdxOfKey(AKeyToCompare, idx); if Result then RemoveByIdx(idx); end; function TGMHashEntryBucket.RemovePointer(const AInstance: Pointer): Boolean; var i: LongInt; begin for i:=0 to FCount-1 do if FEntries^[i] = AInstance then begin RemoveByIdx(i); Result := True; Exit; end; Result := False; end; { --------------------------------- } { ---- TGMHashBitMaskDirectory ---- } { --------------------------------- } constructor TGMHashBitMaskDirectory.Create(const AHashTable: TGMHashTableBase; const AMaxHashBits: Byte; const AHashBitOffs: Byte; const ARefLifeTime: Boolean); begin inherited Create(ARefLifeTime); FHashTable := AHashTable; FHashBitOffs := AHashBitOffs; FMaxHashBits := AMaxHashBits; end; function TGMHashBitMaskDirectory.Obj: TGMHashBitMaskDirectory; begin Result := Self; end; function TGMHashBitMaskDirectory.CalcDirEntryIndex(const AHashCode: TGMHashCode): PtrInt; begin Result := (AHashCode and FHashBitMask) shr (FHashBitOffs + FMaxHashBits - FHashBitCount); end; function TGMHashBitMaskDirectory.FindDirEntry(const AHashCode: TGMHashCode): IUnknown; begin if Length(FDirEntries) = 0 then Exit{$IFDEF FPC}(nil){$ENDIF}; Result := FDirEntries[CalcDirEntryIndex(AHashCode)]; end; procedure TGMHashBitMaskDirectory.ExpandHash(const ADirEntryIdx: LongInt); var NewSize, i: LongInt; DirEntry: IUnknown; begin NewSize := Length(FDirEntries) * 2; if NewSize = 0 then NewSize := 1; SetLength(FDirEntries, NewSize); for i:=NewSize div 2 - 1 downto 0 do begin DirEntry := FDirEntries[i]; FDirEntries[i*2] := DirEntry; FDirEntries[(i*2)+1] := DirEntry; if DirEntry <> nil then Inc(FAssignedCount); // <- original entries have already been counted! end; Inc(FHashBitCount); FHashBitMask := (FHashBitMask shr 1) or (1 shl (FHashBitOffs + FMaxHashBits - 1)); end; function TGMHashBitMaskDirectory.FindBucket(const AHashCode: TGMHashCode; var ABucket: IGMHashEntryBucket): Boolean; var DirEntry: IUnknown; SubDir: IGMHashBitMaskDirectory; begin DirEntry := FindDirEntry(AHashCode); if DirEntry = nil then Result := False else begin if GMQueryInterface(DirEntry, IGMHashBitMaskDirectory, SubDir) then Result := SubDir.Obj.FindBucket(AHashCode, ABucket) else Result := GMQueryInterface(DirEntry, IGMHashEntryBucket, ABucket); end; end; procedure TGMHashBitMaskDirectory.ReHashBuketEntries(const ABucket: IGMHashEntryBucket); var i: LongInt; BucketEntry: Pointer; ReHashCode: TGMHashCode; begin for i:=0 to ABucket.Obj.FCount-1 do begin BucketEntry := ABucket.Obj.FEntries^[i]; ReHashCode := FHashTable.BuildHashCode(FHashTable.EntryAsIntf(BucketEntry)); {$IFDEF DEBUG} if not AddPointer(ReHashCode, BucketEntry) then RaiseError(RStrDuplicateHashEntry, Self); {$ELSE} AddPointer(ReHashCode, BucketEntry); {$ENDIF} end; ABucket.Obj.Clear(False); end; function TGMHashBitMaskDirectory.AddPointer(const AHashCode: TGMHashCode; const ANewEntry: Pointer): Boolean; var DirEntryIdx, i: LongInt; DirEntry: IUnknown; Bucket: IGMHashEntryBucket; SubDir: IGMHashBitMaskDirectory; begin if Length(FDirEntries) = 0 then SetLength(FDirEntries, 1); DirEntryIdx := CalcDirEntryIndex(AHashCode); DirEntry := FDirEntries[DirEntryIdx]; if DirEntry = nil then begin Bucket := TGMHashEntryBucket.Create(FHashTable, True); // FHashBitCount, FDirEntries[DirEntryIdx] := Bucket; Inc(FAssignedCount); end else if GMQueryInterface(DirEntry, IGMHashBitMaskDirectory, SubDir) then begin Result := SubDir.Obj.AddPointer(AHashCode, ANewEntry); Exit; end // <- Attention: Recursive call and Exit here! else GMQueryInterface(DirEntry, IGMHashEntryBucket, Bucket); if Bucket = nil then Result := False else begin if (Bucket.Obj.FCount < cMaxHashBucketSize) or (FHashBitCount + FHashBitOffs >= cMaxHashcodeBits) then Result := Bucket.Obj.AddPointer(ANewEntry) else begin // The bucket is full, extend hash bit mask or add new directories. // There may be more duplicates, but if there are duplicates, neighbours are always duplicate! if (Length(FDirEntries) > 1) and (FDirEntries[DirEntryIdx and $FE] = FDirEntries[DirEntryIdx or 1]) then begin // This two neighbours point to the same bucket, set it nil and re-hash to create a new bucket {TODO: loop only through relevant entries here} for i:=Low(FDirEntries) to High(FDirEntries) do if FDirEntries[i] = Bucket then begin FDirEntries[i] := nil; Dec(FAssignedCount); end; end else if FHashBitCount < FMaxHashBits then begin FDirEntries[DirEntryIdx] := nil; Dec(FAssignedCount); ExpandHash(DirEntryIdx); end else FDirEntries[DirEntryIdx] := TGMHashBitMaskDirectory.Create(FHashTable, Max(FMaxHashBits-1, cMinHashBitsPerDirLevel), FHashBitOffs + FHashBitCount, FRefLifeTime); ReHashBuketEntries(Bucket); Result := AddPointer(AHashCode, ANewEntry); end; end; end; function TGMHashBitMaskDirectory.RemoveByKey(const AHashCode: TGMHashCode; const AKeyToCompare: IUnknown): Boolean; var dirEntryIdx: TBucketDirIdx; dirEntry: IUnknown; SubDir: IGMHashBitMaskDirectory; Bucket: IGMHashEntryBucket; begin Result := False; if Length(FDirEntries) = 0 then Exit; dirEntryIdx := CalcDirEntryIndex(AHashCode); dirEntry := FDirEntries[dirEntryIdx]; if dirEntry <> nil then begin if GMQueryInterface(dirEntry, IGMHashBitMaskDirectory, SubDir) then begin Result := SubDir.Obj.RemoveByKey(AHashCode, AKeyToCompare); if Result and (SubDir.Obj.FAssignedCount = 0) then begin FDirEntries[dirEntryIdx] := nil; Dec(FAssignedCount); end; end else if GMQueryInterface(dirEntry, IGMHashEntryBucket, Bucket) then begin Result := Bucket.Obj.RemoveByKey(AKeyToCompare); {TODO: add case for merging buckets and shrink directory size} if Result and (Bucket.Obj.FCount = 0) then begin FDirEntries[dirEntryIdx] := nil; Dec(FAssignedCount); end; end; end; end; function TGMHashBitMaskDirectory.RemovePointer(const AInstance: Pointer): Boolean; var i: LongInt; subDir: IGMHashBitMaskDirectory; bucket: IGMHashEntryBucket; begin Result := False; for i:=Low(FDirEntries) to High(FDirEntries) do if FDirEntries[i] <> nil then begin if GMQueryInterface(FDirEntries[i], IGMHashBitMaskDirectory, subDir) then begin Result := subDir.Obj.RemovePointer(AInstance); if Result and (subDir.Obj.FAssignedCount = 0) then begin FDirEntries[i] := nil; Dec(FAssignedCount); end; end else if GMQueryInterface(FDirEntries[i], IGMHashEntryBucket, bucket) then begin Result := bucket.Obj.RemovePointer(AInstance); {TODO: add case for merging buckets and shrink directory size} if Result and (bucket.Obj.FCount = 0) then begin FDirEntries[i] := nil; Dec(FAssignedCount); end; end; if Result then Break; end; end; function TGMHashBitMaskDirectory.FirstEntry: Pointer; label SearchDirEntry; var DirEntryIdx: TBucketDirIdx; SubDir: IGMHashBitMaskDirectory; Bucket: IGMHashEntryBucket; begin Result := nil; DirEntryIdx := Low(FDirEntries); SearchDirEntry: while (DirEntryIdx <= High(FDirEntries)) and (FDirEntries[DirEntryIdx] = nil) do Inc(DirEntryIdx); if DirEntryIdx > High(FDirEntries) then Exit; if GMQueryInterface(FDirEntries[DirEntryIdx], IGMHashBitMaskDirectory, SubDir) then Result := SubDir.Obj.FirstEntry else if GMQueryInterface(FDirEntries[DirEntryIdx], IGMHashEntryBucket, Bucket) then if Bucket.Obj.FCount > 0 then Result := Bucket.Obj.FEntries[0] else begin Inc(DirEntryIdx); goto SearchDirEntry; end; end; function TGMHashBitMaskDirectory.LastEntry: Pointer; label SearchDirEntry; var DirEntryIdx: LongInt; SubDir: IGMHashBitMaskDirectory; Bucket: IGMHashEntryBucket; begin Result := nil; DirEntryIdx := High(FDirEntries); SearchDirEntry: while (DirEntryIdx >= Low(FDirEntries)) and (FDirEntries[DirEntryIdx] = nil) do Dec(DirEntryIdx); if DirEntryIdx < Low(FDirEntries) then Exit; if GMQueryInterface(FDirEntries[DirEntryIdx], IGMHashBitMaskDirectory, SubDir) then Result := SubDir.Obj.FirstEntry else if GMQueryInterface(FDirEntries[DirEntryIdx], IGMHashEntryBucket, Bucket) then if Bucket.Obj.FCount > 0 then Result := Bucket.Obj.FEntries[Bucket.Obj.FCount-1] else begin Dec(DirEntryIdx); goto SearchDirEntry; end; end; { -------------------------- } { ---- TGMHashTableBase ---- } { -------------------------- } function TGMHashTableBase.CreateIterator(const AReverse, AConcurrentThreadLock: Boolean): IGMIterator; begin Result := TGMHashTableIterator.Create(Self, AConcurrentThreadLock, AReverse, True); end; function TGMHashTableBase.BuildHashCode(const AKeyToCompare: IUnknown): TGMHashCode; var PIHashCode: IGMHashCode; begin GMCheckQueryInterface(AKeyToCompare, IGMHashCode, PIHashCode, 'BuildHashCode'); Result := PIHashCode.HashCode; end; procedure TGMHashTableBase.Clear(const ANotify: Boolean); var OldCount: LongInt; begin CriticalSection.EnterCriticalSection; try if FRootDirectory = nil then Exit; FRootDirectory := nil; OldCount := FCount; FCount := 0; if ANotify then NotifyAfterCountChanged(OldCount, 0); finally CriticalSection.LeaveCriticalSection; end; end; function TGMHashTableBase.AddPointer(const AHashCode: TGMHashCode; ANewEntry: Pointer): Boolean; begin CriticalSection.EnterCriticalSection; try if FRootDirectory = nil then FRootDirectory := TGMHashBitMaskDirectory.Create(Self, cMaxHashBitsPerDirLevel, 0, True); Result := FRootDirectory.Obj.AddPointer(AHashCode, ANewEntry); if Result then begin Inc(FCount); NotifyAfterCountChanged(FCount-1, FCount); end else if FFreeEntries then FreePointer(ANewEntry); finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMHashTableBase.DoAfterRemove; begin if FRootDirectory.Obj.FAssignedCount = 0 then FRootDirectory := nil; Dec(FCount); NotifyAfterCountChanged(FCount+1, FCount); end; function TGMHashTableBase.RemoveByKey(const AKeyToCompare: IUnknown): Boolean; begin CriticalSection.EnterCriticalSection; try Result := (FRootDirectory <> nil) and FRootDirectory.Obj.RemoveByKey(BuildHashCode(AKeyToCompare), AKeyToCompare); if Result then DoAfterRemove; finally CriticalSection.LeaveCriticalSection; end; end; function TGMHashTableBase.RemovePointer(const AInstance: Pointer): Boolean; begin CriticalSection.EnterCriticalSection; try Result := (FRootDirectory <> nil) and FRootDirectory.Obj.RemovePointer(AInstance); if Result then DoAfterRemove; finally CriticalSection.LeaveCriticalSection; end; end; function TGMHashTableBase.Find(const AKeyToCompare: IUnknown; out AEntry): Boolean; var PIBucket: IGMHashEntryBucket; begin CriticalSection.EnterCriticalSection; try Result := (FRootDirectory <> nil) and FRootDirectory.Obj.FindBucket(BuildHashCode(AKeyToCompare), PIBucket) and PIBucket.Obj.FindKey(AKeyToCompare, AEntry); finally CriticalSection.LeaveCriticalSection; end; end; function TGMHashTableBase.FindNearest(const AKeyToCompare: IUnknown; out AEntry): Boolean; {$IFDEF FPC}{$push}{$WARN 5033 off : Function result variable does not seem to be initialized}{$ENDIF} begin raise Exception.Create(GMFormat(RStrNoNearestInHashFmt, [ClassName])); end; {$IFDEF FPC}{$pop}{$ENDIF} function TGMHashTableBase.FirstEntry: Pointer; begin CriticalSection.EnterCriticalSection; try if FRootDirectory = nil then Result := nil else Result := FRootDirectory.Obj.FirstEntry finally CriticalSection.LeaveCriticalSection; end; end; function TGMHashTableBase.LastEntry: Pointer; begin CriticalSection.EnterCriticalSection; try if FRootDirectory = nil then Result := nil else Result := FRootDirectory.Obj.LastEntry finally CriticalSection.LeaveCriticalSection; end; end; { ------------------------------ } { ---- TGMHashTableIterator ---- } { ------------------------------ } // Some Statistics, useful for parameter optimization //var // BucketCount: LongInt = 0; // SingleBucketCount: LongInt = 0; function TGMHashTableIterator.NextEntry(out AEntry): Boolean; var StackData: PHashIteratorStackRec; DirEntry: IUnknown; SubDir: IGMHashBitMaskDirectory; //Bucket: IGMHashEntryBucket; procedure NextIndex(var AIndex: LongInt); begin if FReverse then Dec(AIndex) else Inc(AIndex); end; function TellBucketEntry: Boolean; begin if (FCurrentBucket = nil) or (FCurrentBucketEntryIdx < 0) or (FCurrentBucketEntryIdx >= FCurrentBucket.Obj.FCount) then Result := False else begin (FCollection as TGMHashTableBase).AssignOutEntry(FCurrentBucket.Obj.FEntries[FCurrentBucketEntryIdx], AEntry); if (FReverse and (FCurrentBucketEntryIdx > 0)) or (not FReverse and (FCurrentBucketEntryIdx < FCurrentBucket.OBj.FCount-1)) then NextIndex(FCurrentBucketEntryIdx) else begin FCurrentBucketEntryIdx := -1; NextIndex(StackData.DirEntryIdx); with StackData^ do while (DirEntryIdx >= Low(Directory.Obj.FDirEntries)) and (DirEntryIdx <= High(Directory.Obj.FDirEntries)) and (Directory.Obj.FDirEntries[DirEntryIdx] = FCurrentBucket) do NextIndex(StackData.DirEntryIdx); FCurrentBucket := nil; end; Result := True; end; end; begin Result := False; if Length(FDirStack) = 0 then Exit; StackData := @FDirStack[High(FDirStack)]; if StackData.Directory = nil then Exit; if TellBucketEntry then begin Result := True; Exit; end; with StackData^ do while (DirEntryIdx >= Low(Directory.Obj.FDirEntries)) and (DirEntryIdx <= High(Directory.Obj.FDirEntries)) and (Directory.Obj.FDirEntries[DirEntryIdx] = nil) do NextIndex(StackData.DirEntryIdx); if (StackData.DirEntryIdx < Low(StackData.Directory.Obj.FDirEntries)) or (StackData.DirEntryIdx > High(StackData.Directory.Obj.FDirEntries)) then begin SetLength(FDirStack, Length(FDirStack)-1); if Length(FDirStack) = 0 then Exit; NextIndex(FDirStack[High(FDirStack)].DirEntryIdx); Result := NextEntry(AEntry); Exit; end; DirEntry := StackData.Directory.Obj.FDirEntries[StackData.DirEntryIdx]; if GMQueryInterface(DirEntry, IGMHashEntryBucket, FCurrentBucket) then begin //Inc(BucketCount); //if FCurrentBucket.Obj.FCount = 1 then Inc(SingleBucketCount); if FReverse then FCurrentBucketEntryIdx := FCurrentBucket.Obj.FCount-1 else FCurrentBucketEntryIdx := 0; if TellBucketEntry then Result := True else begin FCurrentBucket := nil; NextIndex(StackData.DirEntryIdx); Result := NextEntry(AEntry); end; end else if GMQueryInterface(DirEntry, IGMHashBitMaskDirectory, SubDir) then begin SetLength(FDirStack, Length(FDirStack)+1); FDirStack[High(FDirStack)].Directory := SubDir; if FReverse then FDirStack[High(FDirStack)].DirEntryIdx := High(SubDir.Obj.FDirEntries) else FDirStack[High(FDirStack)].DirEntryIdx := 0; Result := NextEntry(AEntry); end; end; procedure TGMHashTableIterator.Reset; var RootDir: IGMHashBitMaskDirectory; begin if not (FCollection is TGMHashTableBase) or ((FCollection as TGMHashTableBase).FRootDirectory = nil) then SetLength(FDirStack, 0) else begin SetLength(FDirStack, 1); RootDir := (FCollection as TGMHashTableBase).FRootDirectory; FDirStack[Low(FDirStack)].Directory := RootDir; if FReverse then FDirStack[Low(FDirStack)].DirEntryIdx := High(RootDir.Obj.FDirEntries) else FDirStack[Low(FDirStack)].DirEntryIdx := 0; end; FCurrentBucket := nil; FCurrentBucketEntryIdx := -1; end; { ------------------------- } { ---- TGMObjHashTable ---- } { ------------------------- } constructor TGMObjHashTable.Create(const AFreeEntries, AAcceptDuplicates: Boolean; const ACompareFunc: TGMIntfCompareFunc; const ARefLifeTime: Boolean); begin inherited Create(AAcceptDuplicates, ACompareFunc, ARefLifeTime); FFreeEntries := AFreeEntries; end; procedure TGMObjHashTable.AssignOutEntry(const AEntryPtr: Pointer; out AEntry); begin TObject(AEntry) := AEntryPtr; end; procedure TGMObjHashTable.FreePointer(var AEntryPtr: Pointer); begin //if AEntryPtr <> nil then TObject(AEntryPtr).Free; GMFreeAndNil(TObject(AEntryPtr)); end; function TGMObjHashTable.EntryAsIntf(const AEntryPtr: Pointer): IUnknown; begin Result := GMObjAsIntf(TObject(AEntryPtr)); end; function TGMObjHashTable.Add(const ANewEntry: TObject; const AReplaceIfExists: Boolean): TObject; var HashCode: TGMHashCode; UnkEntry: IUnknown; begin UnkEntry := GMObjAsIntf(ANewEntry); HashCode := BuildHashCode(UnkEntry); UnkEntry := nil; // <- Needed when borlands memory manager is used if AddPointer(HashCode, ANewEntry) then Result := ANewEntry else Result := nil; end; function TGMObjHashTable.RemoveByInstance(const AObj: TObject): Boolean; begin Result := RemovePointer(Pointer(AObj)); end; function TGMObjHashTable.First: TObject; begin AssignOutEntry(FirstEntry, Result); end; function TGMObjHashTable.Last: TObject; begin AssignOutEntry(LastEntry, Result); end; { -------------------------- } { ---- TGMIntfHashTable ---- } { -------------------------- } constructor TGMIntfHashTable.Create(const AAcceptDuplicates: Boolean; const ACompareFunc: TGMIntfCompareFunc; const ARefLifeTime: Boolean); begin inherited Create(AAcceptDuplicates, ACompareFunc, ARefLifeTime); FFreeEntries := True; end; procedure TGMIntfHashTable.FreePointer(var AEntryPtr: Pointer); begin //if AEntryPtr <> nil then IUnknown(AEntryPtr) := nil; end; procedure TGMIntfHashTable.AssignOutEntry(const AEntryPtr: Pointer; out AEntry); begin IUnknown(AEntry) := IUnknown(AEntryPtr); end; function TGMIntfHashTable.EntryAsIntf(const AEntryPtr: Pointer): IUnknown; begin Result := IUnknown(AEntryPtr); end; function TGMIntfHashTable.Add(const ANewEntry: IUnknown; const AReplaceIfExists: Boolean): IUnknown; var itemHolder: IUnknown; begin // // Always add the real IUnknown representation for proper iterator return values, and put an additional RefCount for the duration of this call // if not GMQueryInterface(ANewEntry, IUnknown, itemHolder) then Exit{$IFDEF FPC}(nil){$ENDIF}; // // Always _AddRef! Either the entry gets stored or, if it is a duplicate, it will be released again. // itemHolder._AddRef; if AddPointer(BuildHashCode(itemHolder), Pointer(itemHolder)) then Result := ANewEntry else Result := nil; end; function TGMIntfHashTable.RemoveByInstance(const AIntf: IUnknown): Boolean; var unk: IUnknown; begin Result := GMQueryInterface(AIntf, IUnknown, unk) and RemovePointer(Pointer(unk)); // <- use real Iunknown representation for proper object identification end; function TGMIntfHashTable.First: IUnknown; begin AssignOutEntry(FirstEntry, Result); end; function TGMIntfHashTable.Last: IUnknown; begin AssignOutEntry(LastEntry, Result); end; {$IFDEF HAS_GENERICS} { ------------------------- } { ---- Global Routines ---- } { ------------------------- } function GMGenericCompareUsingOperators<T>(const AValueA, AValueB: T): TGMCompareResult; begin if AValueA < AValueB then Result := crALessThanB else if AValueA = AValueB then Result := crAEqualToB else Result := crAGreaterThanB end; { ----------------------------------- } { ---- TGMPlainGenericCollection ---- } { ----------------------------------- } procedure TGMPlainGenericCollection.CheckSorted(const ASortedReqired, ASorted: Boolean; const AOperationName: TGMString); begin if ASortedReqired and not ASorted then RaiseError(GMFormat(RStrCollectionNotSortedFmt, [AOperationName]), Self, cStrCheckSorted); if not ASortedReqired and ASorted then RaiseError(GMFormat(RStrCollectionSortedFmt, [AOperationName]), Self, cStrCheckUnsorted); end; function TGMPlainGenericCollection.ArrayGrowDelta(const ACurrentCapacity: PtrInt): PtrInt; begin Result := GrowDelta(ACurrentCapacity); end; function TGMPlainGenericCollection.MsgStrArrayIndex: TGMString; begin Result := RStrArrayIndex; end; { ----------------------------------- } { ---- TGMGenericIteratorBase<T> ---- } { ----------------------------------- } constructor TGMGenericIteratorBase<T>.Create(const ACollection: IUnknown; const AReverse: Boolean; const AConcurrentThreadLock: Boolean; const ARefLifeTime: Boolean); begin inherited Create(ARefLifeTime); FCollectionHolder := ACollection; FReverse := AReverse; //if AConcurrentThreadLock then FThreadLock := TGMCriticalSectionLock.Create(ACollection); if AConcurrentThreadLock then FThreadLock.Lock(ACollection); Reset; end; { ------------------------------------- } { ---- TGMGenericCollectionBase<T> ---- } { ------------------------------------- } constructor TGMGenericCollectionBase<T>.Create(const AAcceptDuplicates: Boolean; const ACompareFunc: TGMGenericCompareFunc<T>; const AFinalizeEntryProc: TGMGenericFinalizeEntryProc<T>; const ARefLifeTime: Boolean); begin inherited Create(ARefLifeTime); FCriticalSection := TGMCriticalSection.Create(True); FCompareFunc := ACompareFunc; FFinalizeEntryProc := AFinalizeEntryProc; FAcceptDuplicates := AAcceptDuplicates; //FContainsManagedType := IsManaged(TypeInfo(T)); end; destructor TGMGenericCollectionBase<T>.Destroy; begin Clear(False); inherited; end; function TGMGenericCollectionBase<T>.NullEntry: T; begin //if not FContainsManagedType then Result := Default(T); //FillByte(Result, SizeOf(Result), 0); end; function TGMGenericCollectionBase<T>.IsValidIndex(const AIndex: PtrInt): Boolean; begin CriticalSection.EnterCriticalSection; try Result := (AIndex >= 0) and (AIndex < FCount) finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMGenericCollectionBase<T>.SetCompareFunc(const ACompareFunc: TGMGenericCompareFunc<T>); begin CriticalSection.EnterCriticalSection; try FCompareFunc := ACompareFunc; finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericCollectionBase<T>.GetCount: PtrInt; stdcall; begin CriticalSection.EnterCriticalSection; try Result := FCount; finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericCollectionBase<T>.IsEmpty: Boolean; begin CriticalSection.EnterCriticalSection; try Result := FCount <= 0; finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericCollectionBase<T>.CompareEntries(const AEntryA, AEntryB: T): TGMCompareResult; begin if Assigned(FCompareFunc) then Result := FCompareFunc(AEntryA, AEntryB) else Result := GMCompareMemory(@AEntryA, @AEntryB, SizeOf(T)); end; procedure TGMGenericCollectionBase<T>.NotifyAfterCountChanged(const AOldCount, ANewCount: PtrInt); begin if Assigned(FOnAfterCountChanged) then FOnAfterCountChanged(Self, AOldCount, ANewCount); end; procedure TGMGenericCollectionBase<T>.NotifyAfterAddItem(const AEntry: T; const AIndex: PtrInt); begin if Assigned(FOnAfterAddItem) then FOnAfterAddItem(Self, AEntry, AIndex); end; procedure TGMGenericCollectionBase<T>.NotifyBeforeRemoveItem(const AEntry: T; const AIndex: PtrInt); begin if Assigned(FOnBeforeRemoveItem) then FOnBeforeRemoveItem(Self, AEntry, AIndex); end; procedure TGMGenericCollectionBase<T>.SetOnAfterCountChanged(const AValue: TGMCountChangedProc); begin FOnAfterCountChanged := AValue; end; procedure TGMGenericCollectionBase<T>.SetOnAfterAddItem(const AValue: TGMGenericEntryAddRemoveProc<T>); begin FOnAfterAddItem := AValue; end; procedure TGMGenericCollectionBase<T>.SetOnBeforeRemoveItem(const AValue: TGMGenericEntryAddRemoveProc<T>); begin FOnBeforeRemoveItem := AValue; end; { -------------------------------------- } { ---- TGMGenericArrayCollection<T> ---- } { -------------------------------------- } constructor TGMGenericArrayCollection<T>.Create(const AAcceptDuplicates: Boolean; const ASorted: Boolean; const ACompareFunc: TGMGenericCompareFunc<T>; const AFinalizeEntryProc: TGMGenericFinalizeEntryProc<T>; const ARefLifeTime: Boolean); begin inherited Create(AAcceptDuplicates, ACompareFunc, AFinalizeEntryProc, ARefLifeTime); FSorted := ASorted; end; function TGMGenericArrayCollection<T>.GetSorted: Boolean; begin Result := FSorted; end; procedure TGMGenericArrayCollection<T>.QuickSort(L, R: PtrInt); var i, j, m: PtrInt; e: T; begin i := L; j := R; m := (L + R) shr 1; while i <= j do begin while CompareEntries(FEntries[i], FEntries[m]) = crALessThanB do Inc(i); while CompareEntries(FEntries[j], FEntries[m]) = crAGreaterThanB do Dec(j); if i <= j then begin if i <> j then // and CompareFunc(EntryAsIntf(i), EntryAsIntf(j)) <> crAEqualToB begin e := FEntries[i]; FEntries[i] := FEntries[j]; FEntries[j] := e; // <- exchange i <-> j if m = i then m := j else if m = j then m := i; // <- if [m] has been exchanged update m end; Inc(i); Dec(j); end; end; if L < j then QuickSort(L, j); if i < R then QuickSort(i, R); end; procedure TGMGenericArrayCollection<T>.Sort; begin CriticalSection.EnterCriticalSection; try if (FEntries <> nil) and (FCount > 0) then QuickSort(0, FCount-1); finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMGenericArrayCollection<T>.SetSorted(const AValue: Boolean); begin CriticalSection.EnterCriticalSection; try if AValue <> FSorted then begin FSorted := AValue; if AValue then Sort; end; finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericArrayCollection<T>.GetEntry(const AIndex: PtrInt): T; begin CriticalSection.EnterCriticalSection; try GMCheckIntRange(MsgStrArrayIndex, AIndex, 0, FCount-1, Self, 'TGMGenericArrayCollection.GetEntry'); Result := FEntries[AIndex]; finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMGenericArrayCollection<T>.SetEntry(const AIndex: PtrInt; const AValue: T); begin CriticalSection.EnterCriticalSection; try GMCheckIntRange(MsgStrArrayIndex, AIndex, 0, FCount-1, Self, 'TGMGenericArrayCollection.SetEntry'); if Assigned(FFinalizeEntryProc) then FFinalizeEntryProc(FEntries[AIndex]); FEntries[AIndex] := AValue; finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMGenericArrayCollection<T>.SetCapacity(ANewCapacity: PtrInt); begin CriticalSection.EnterCriticalSection; try //if (ANewCapacity < 0) or (ANewCapacity > cMaxPtrArraySize) then RaiseError(GMFormat(RStrListCapacityError, [ANewCapacity, cMaxPtrArraySize]), Self, 'SetCapacity'); if ANewCapacity < 0 then ANewCapacity := 0; if ANewCapacity <> FCapacity then begin SetLength(FEntries, ANewCapacity); FCapacity := ANewCapacity; end; finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericArrayCollection<T>.CalcInsertIdx(const AEntry: T): PtrInt; function _InsertIdx(L, R: PtrInt): PtrInt; var M: PtrInt; begin //if not Assigned(CompareFunc) then Result := FCount else if L >= R then Result := L else begin M := (L + R) shr 1; case CompareEntries(AEntry, FEntries[M]) of crAEqualToB, crALessThanB: Result := _InsertIdx(L, M); crAGreaterThanB: if L = M then Result := R else Result := _InsertIdx(M, R); else {$IFNDEF STANDALONE_COLLECTIONS} raise EGMException.ObjError(RStrInvalidCompareResult, Self, {$I %CurrentRoutine%}); {$ELSE} raise Exception.Create(GMStringJoin(GMStringJoin(GMObjName(Self), cStrErrNameSep, {$I %CurrentRoutine%}), ': ', RStrInvalidCompareResult)); {$ENDIF} end; end; end; begin CriticalSection.EnterCriticalSection; try if not FSorted then Result := FCount else Result := _InsertIdx(0, FCount); finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericArrayCollection<T>.IndexOf(const AEntryToFind: T): PtrInt; begin CriticalSection.EnterCriticalSection; try if not FSorted then begin for Result := 0 to FCount-1 do if CompareEntries(FEntries[Result], AEntryToFind) = crAEqualToB then Exit; Result := cInvalidItemIdx; end else begin Result := CalcInsertIdx(AEntryToFind); if not IsValidIndex(Result) or (CompareEntries(FEntries[Result], AEntryToFind) <> crAEqualToB) then Result := cInvalidItemIdx; end; finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMGenericArrayCollection<T>.InternalInsertEntry(const AEntry: T; const AIndex: PtrInt); begin GMCheckIntRange(MsgStrArrayIndex, AIndex, 0, FCount, Self, {$I %CurrentRoutine%}); if FCount = FCapacity then SetCapacity(FCapacity + ArrayGrowDelta(FCapacity)); if AIndex < FCount then System.Move(FEntries[AIndex], FEntries[AIndex + 1], (FCount - AIndex) * SizeOf(T)); FillByte(FEntries[AIndex], SizeOf(T), 0); // <- Important, following assignment will interpret this entry accordingly in case of managed types! FEntries[AIndex] := AEntry; Inc(FCount); NotifyAfterAddItem(AEntry, AIndex); NotifyAfterCountChanged(FCount-1, FCount); end; function TGMGenericArrayCollection<T>.IsDuplicate(const AEntry: T; var AIndex: PtrInt): Boolean; var idx: PtrInt; begin Result := False; if not FAcceptDuplicates then begin if FSorted then Result := IsValidIndex(AIndex) and (CompareEntries(FEntries[AIndex], AEntry) = crAEqualToB) else for idx:=0 to FCount-1 do if CompareEntries(FEntries[idx], AEntry) = crAEqualToB then begin AIndex := idx; Result := True; Break; end; end; end; function TGMGenericArrayCollection<T>.InternalInsert(const AEntry: T; var AIndex: PtrInt; const AReplaceIfExists: Boolean = False): Boolean; var doInsert: Boolean; begin doInsert := FAcceptDuplicates or not IsDuplicate(AEntry, AIndex); Result := doInsert or AReplaceIfExists; if doInsert then try InternalInsertEntry(AEntry, AIndex); except if Assigned(FFinalizeEntryProc) then FFinalizeEntryProc(AEntry); raise; end else if AReplaceIfExists then begin if Assigned(FFinalizeEntryProc) then FFinalizeEntryProc(FEntries[AIndex]); FEntries[AIndex] := AEntry; end else if Assigned(FFinalizeEntryProc) then FFinalizeEntryProc(AEntry); end; function TGMGenericArrayCollection<T>.CreateIterator(const AReverse: Boolean; const AConcurrentThreadLock: Boolean): IGMGenericIterator<T>; begin Result := TGMGenericArrayIterator<T>.Create(Self, AReverse, AConcurrentThreadLock); end; procedure TGMGenericArrayCollection<T>.SetCompareFunc(const ACompareFunc: TGMGenericCompareFunc<T>); var oldCmpFunc: TGMGenericCompareFunc<T>; begin oldCmpFunc := FCompareFunc; inherited SetCompareFunc(ACompareFunc); if Sorted and (@ACompareFunc <> @oldCmpFunc) then Sort; end; function TGMGenericArrayCollection<T>.IndexOfNearest(const AEntryToFind: T): PtrInt; begin CriticalSection.EnterCriticalSection; try CheckSorted(True, FSorted, 'IndexOfNearest'); Result := GMBoundedInt(CalcInsertIdx(AEntryToFind), 0, FCount-1); if not IsValidIndex(Result) then Result := cInvalidItemIdx; finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericArrayCollection<T>.Add(const ANewEntry: T; const AReplaceIfExists: Boolean): T; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try idx := CalcInsertIdx(ANewEntry); InternalInsert(ANewEntry, idx, AReplaceIfExists); //if not InternalInsert(ANewEntry, idx, AReplaceIfExists) and Assigned(FFinalizeEntryProc) then FFinalizeEntryProc(ANewEntry); Result := FEntries[idx]; finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericArrayCollection<T>.AddIdx(const ANewEntry: T; const AReplaceIfExists: Boolean): PtrInt; begin CriticalSection.EnterCriticalSection; try Result := CalcInsertIdx(ANewEntry); InternalInsert(ANewEntry, Result, AReplaceIfExists); //if not InternalInsert(ANewEntry, Result, AReplaceIfExists) and Assigned(FFinalizeEntryProc) then FFinalizeEntryProc(ANewEntry); finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericArrayCollection<T>.Insert(const ANewEntry: T; const AIndex: PtrInt; const AReplaceIfExists: Boolean): T; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try CheckSorted(False, FSorted, 'Insert'); //if FSorted then Result := Add(ANewEntry, AReplaceIfExists) else // begin idx := AIndex; InternalInsert(ANewEntry, idx, AReplaceIfExists); //if not InternalInsert(ANewEntry, idx, AReplaceIfExists) and Assigned(FFinalizeEntryProc) then FFinalizeEntryProc(ANewEntry); Result := FEntries[idx]; //end; finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericArrayCollection<T>.Contains(const AEntryToFind: T): Boolean; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try idx := IndexOf(AEntryToFind); Result := IsValidIndex(idx); finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericArrayCollection<T>.Find(const AEntryToFind: T; var AFoundEntry: T): Boolean; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try idx := IndexOf(AEntryToFind); Result := IsValidIndex(idx); if Result then AFoundEntry := FEntries[idx]; finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericArrayCollection<T>.FindNearest(const AEntryToFind: T; var AEntry: T): Boolean; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try idx := IndexOfNearest(AEntryToFind); Result := IsValidIndex(idx); if Result then AEntry := FEntries[idx]; finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMGenericArrayCollection<T>.RemoveByIdx(const AIndex: PtrInt; ADelCount: PtrInt); var i: PtrInt; buffer: Pointer; bufferSize: Cardinal; begin CriticalSection.EnterCriticalSection; try GMCheckIntRange(MsgStrArrayIndex, AIndex, 0, FCount-1, Self, {$I %CurrentRoutine%}); ADelCount := Min(ADelCount, FCount - AIndex); if ADelCount <= 0 then Exit; // <- GMCheckIntRange should never raise with this for i:=AIndex to AIndex + ADelCount-1 do begin NotifyBeforeRemoveItem(FEntries[i], i); if Assigned(FFinalizeEntryProc) then FFinalizeEntryProc(FEntries[i]); FEntries[i] := Default(T); // <- free managed types end; Dec(FCount, ADelCount); if AIndex < FCount then begin bufferSize := ADelCount * SizeOf(T); buffer := GetMem(bufferSize); try // // Move deleted entries to the end, shrinking capacity will eventually apply automatic compiler finalization for managed types // System.Move(FEntries[AIndex], buffer^, bufferSize); System.Move(FEntries[AIndex + ADelCount], FEntries[AIndex], (FCount - AIndex) * SizeOf(T)); System.Move(buffer^, FEntries[FCount], bufferSize); finally FreeMem(buffer); end; end; if FCapacity - FCount >= ArrayGrowDelta(FCapacity) then SetCapacity(FCapacity - ArrayGrowDelta(FCapacity)); NotifyAfterCountChanged(FCount + ADelCount, FCount); finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericArrayCollection<T>.RemoveByKey(const AKeyToCompare: T): Boolean; var idx: PtrInt; begin CriticalSection.EnterCriticalSection; try idx := IndexOf(AKeyToCompare); Result := IsValidIndex(idx); if Result then RemoveByIdx(idx, 1); finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMGenericArrayCollection<T>.Clear(const ANotify: Boolean); var i, oldCount: PtrInt; finalyzeEntries: Boolean; begin CriticalSection.EnterCriticalSection; try // Better free in reverse order finalyzeEntries := Assigned(FFinalizeEntryProc); if ANotify or finalyzeEntries then for i:=FCount-1 downto 0 do begin if ANotify then NotifyBeforeRemoveItem(FEntries[i], i); if finalyzeEntries then FFinalizeEntryProc(FEntries[i]); end; oldCount := FCount; SetCapacity(0); // <- will free managed types FCount := 0; if ANotify then NotifyAfterCountChanged(oldCount, 0); finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMGenericArrayCollection<T>.Rotate(ADelta: PtrInt; const AStartIdx: PtrInt); var buffer: Pointer; bufferSize: PtrInt; begin if FCount < 2 then Exit; CriticalSection.EnterCriticalSection; try CheckSorted(False, FSorted, {$I %CurrentRoutine%}); //if Abs(ADelta) >= FCount - AStartIdx then RaiseError(GMFormat(RStrInvalidRotateDelta, [ADelta]), Self, 'Rotate'); ADelta := ADelta mod FCount; GMCheckIntRange(MsgStrArrayIndex, AStartIdx, 0, FCount-1, Self, {$I %CurrentRoutine%}); //if AStartIdx >= FCount then RaiseError(GMFormat(RStrInvalidRotateStartPos, [AStartIdx]), Self, 'Rotate'); bufferSize := Abs(ADelta) * SizeOf(T); buffer := GetMem(bufferSize); try if ADelta > 0 then begin //buffer := GetMem(bufferSize); //try System.Move(FEntries[FCount-ADelta], buffer^, bufferSize); System.Move(FEntries[AStartIdx], FEntries[ADelta+AStartIdx], (FCount-ADelta-AStartIdx) * SizeOf(T)); System.Move(buffer^, FEntries[AStartIdx], bufferSize); //finally // FreeMem(buffer); //end; end; if ADelta < 0 then begin //buffer := GetMem(bufferSize); //try System.Move(FEntries[AStartIdx], buffer^, bufferSize); System.Move(FEntries[AStartIdx-ADelta], FEntries[AStartIdx], (FCount+ADelta-AStartIdx) * SizeOf(T)); System.Move(buffer^, FEntries[FCount+ADelta], bufferSize); //finally // FreeMem(buffer); //end; end; finally FreeMem(buffer); end; finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMGenericArrayCollection<T>.Exchange(const AIndex1, AIndex2: PtrInt); var tmpItem: T; begin CriticalSection.EnterCriticalSection; try if AIndex1 <> AIndex2 then begin CheckSorted(False, FSorted, {$I %CurrentRoutine%}); GMCheckIntRange(MsgStrArrayIndex, AIndex1, 0, FCount-1, Self, {$I %CurrentRoutine%}); GMCheckIntRange(MsgStrArrayIndex, AIndex2, 0, FCount-1, Self, {$I %CurrentRoutine%}); tmpItem := FEntries[AIndex1]; FEntries[AIndex1] := FEntries[AIndex2]; FEntries[AIndex2] := tmpItem; end; finally CriticalSection.LeaveCriticalSection; end; end; procedure TGMGenericArrayCollection<T>.Reverse; var i: PtrInt; begin CriticalSection.EnterCriticalSection; try if FCount > 1 then for i:=0 to FCount-1 shr 1 do Exchange(i, FCount-1-i); finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericArrayCollection<T>.First: T; begin CriticalSection.EnterCriticalSection; try if IsEmpty then Result := NullEntry else Result := FEntries[0]; // Low(FEntries) finally CriticalSection.LeaveCriticalSection; end; end; function TGMGenericArrayCollection<T>.Last: T; begin CriticalSection.EnterCriticalSection; try if IsEmpty then Result := NullEntry else Result := FEntries[FCount-1]; finally CriticalSection.LeaveCriticalSection; end; end; { ------------------------------------ } { ---- TGMGenericArrayIterator<T> ---- } { ------------------------------------ } constructor TGMGenericArrayIterator<T>.Create(const ACollection: IGMGenericArrayCollection<T>; const AReverse: Boolean; const AConcurrentThreadLock: Boolean; const ARefLifeTime: Boolean); begin inherited Create(ACollection, AReverse, AConcurrentThreadLock, ARefLifeTime); FCollection := ACollection; end; function TGMGenericArrayIterator<T>.NextEntry(var AEntry: T): Boolean; begin Result := FCollection.IsValidIndex(FCurrentIdx); if Result then begin AEntry := FCollection.Entries[FCurrentIdx]; if FReverse then Dec(FCurrentIdx) else Inc(FCurrentIdx); end; end; procedure TGMGenericArrayIterator<T>.Reset; begin if FReverse then FCurrentIdx := FCollection.Count-1 else FCurrentIdx := 0; end; {$ENDIF} // HAS_GENERICS { ----------------------- } { ---- TGMIntegerMap ---- } { ----------------------- } constructor TGMIntegerMap.Create(const ANotifyProc: TNotifyIntMapChangeProc; const ARefLifeTime: Boolean = False); begin inherited Create(ARefLifeTime); FChangeNotifyProc := ANotifyProc; end; destructor TGMIntegerMap.Destroy; begin SetCapacity(0); inherited Destroy; end; function TGMIntegerMap.Obj: TGMIntegerMap; begin Result := Self; end; procedure TGMIntegerMap.IntMapChanged(const AValue: PtrInt); begin if Assigned(FChangeNotifyProc) then FChangeNotifyProc(AValue); end; function TGMIntegerMap.MapIntegerOnInteger(const AMapKeyValue: PtrInt): PtrInt; begin Result := Values[AMapKeyValue]; end; function TGMIntegerMap.IsEmpty: Boolean; begin Result := Count <= 0; end; function TGMIntegerMap.GetCount: PtrInt; stdcall; begin Result := Count; end; procedure TGMIntegerMap.SetCapacity(const ANewCapacity: PtrInt); begin if (ANewCapacity < 0) or (ANewCapacity > cMaxIntArraySize) then RaiseError(GMFormat(RStrListCapacityError, [ANewCapacity, cMaxIntArraySize]), Self, {$I %CurrentRoutine%}); if ANewCapacity <> FCapacity then begin ReallocMem(FValues, ANewCapacity * SizeOf(PtrInt)); FCapacity := ANewCapacity; end; end; function TGMIntegerMap.GetMappedValue(AIndex: PtrInt): PtrInt; begin GMCheckIntRange(RStrMapIndex, AIndex, 0, Count-1, Self, {$I %CurrentRoutine%}); Result := FValues^[AIndex]; end; function TGMIntegerMap.CalcInsertIdx(const AValue: PtrInt): PtrInt; function _InsertIdx(L, R: PtrInt): PtrInt; var M: LongInt; begin if L >= R then Result := L else begin M := (L + R) shr 1; if AValue <= FValues^[M] then Result := _InsertIdx(L, M) else if L = M then Result := R else Result := _InsertIdx(M, R); end; end; begin {if IsEmpty then Result := 0 else} Result := _InsertIdx(0, Count); end; procedure TGMIntegerMap.Add(const AValue: PtrInt); var idx: PtrInt; begin if Assigned(OnDecideAddValue) and not OnDecideAddValue(AValue) then Exit; idx := CalcInsertIdx(AValue); if (idx = Count) or (FValues^[idx] <> AValue) then begin GMCheckIntRange(RStrMapIndex, idx, 0, FCount, Self, {$I %CurrentRoutine%}); if FCount = FCapacity then SetCapacity(FCapacity + GrowDelta(FCapacity)); if idx < FCount then System.Move(FValues^[idx], FValues^[idx + 1], (FCount - idx) * SizeOf(PtrInt)); FValues^[idx] := AValue; Inc(FCount); IntMapChanged(AValue); end; end; procedure TGMIntegerMap.RemoveByIdx(const AIndex: PtrInt); var val: PtrInt; begin GMCheckIntRange(RStrMapIndex, AIndex, 0, FCount-1, Self, {$I %CurrentRoutine%}); val := FValues^[AIndex]; Dec(FCount); if AIndex < FCount then System.Move(FValues^[AIndex + 1], FValues^[AIndex], (FCount - AIndex) * SizeOf(PtrInt)); if FCapacity - FCount >= GrowDelta(FCapacity) then SetCapacity(FCapacity - GrowDelta(FCapacity)); IntMapChanged(val); end; procedure TGMIntegerMap.Remove(const AValue: PtrInt); var idx: PtrInt; begin idx := CalcInsertIdx(AValue); if (idx < Count) and (FValues^[idx] = AValue) then RemoveByIdx(idx); end; procedure TGMIntegerMap.Toggle(const AValue: PtrInt); begin if Contains(AValue) then Remove(AValue) else Add(AValue); end; procedure TGMIntegerMap.AddRange(AValue1, AValue2: PtrInt); var i, tmp: PtrInt; begin if AValue1 > AValue2 then begin tmp := AValue1; AValue1 := AValue2; AValue2 := tmp; end; for i:=AValue1 to AValue2 do Add(i); end; procedure TGMIntegerMap.SetRange(AValue1, AValue2: PtrInt); var tmp: PtrInt; begin if AValue1 > AValue2 then begin tmp := AValue1; AValue1 := AValue2; AValue2 := tmp; end; while not IsEmpty and (FValues[0] < AValue1) do RemoveByIdx(0); while not IsEmpty and (FValues[Count-1] > AValue2) do RemoveByIdx(Count-1); AddRange(AValue1, AValue2); end; function TGMIntegerMap.Contains(const AValue: PtrInt): Boolean; var idx: PtrInt; begin idx := CalcInsertIdx(AValue); Result := (idx < Count) and (FValues^[idx] = AValue); end; procedure TGMIntegerMap.Clear(const ANotify: Boolean); var i: PtrInt; valArr: array of PtrInt; begin if ANotify then begin SetLength(valArr, Count); for i:=0 to Count-1 do valArr[i] := Values[i]; end; SetCapacity(0); FCount := 0; if ANotify then for i:=Low(valArr) to High(valArr) do IntMapChanged(valArr[i]); end; end.