{ Summary: Collection of common datastructures ## $Id: dtypes.pas,v 1.14 2004/03/26 17:00:15 druid Exp $ } unit dtypes; interface uses Variants, Classes, SysUtils; {$M+} type { Container class for strings } GString = class private _value : string; published constructor Create(const value : string); property value : string read _value write _value; { The string value } end; { Container class for integers } GInteger = class private _value : integer; published constructor Create(const value : integer); property value : integer read _value write _value; { The integer value } end; { Container class for bitvectors } GBitVector = class private _value : cardinal; published constructor Create(const value : cardinal); function isBitSet(const bit : cardinal) : boolean; procedure setBit(const bit : cardinal); procedure removeBit(const bit : cardinal); property value : cardinal read _value write _value; { The integer value (bitvector) } end; { Base class for list nodes } GListNode = class private _prev : GListNode; { Pointer to previous node in list } _next : GListNode; { Pointer to next node in list } _element : TObject; { Pointer to element } public property prev : GListNode read _prev write _prev; property next : GListNode read _next write _next; published constructor Create(e : pointer; p, n : GListNode); property element : TObject read _element write _element; end; { Abstract base class for iterators } GIterator = class published function getCurrent() : TObject; virtual; abstract; { Abstract getCurrent() } function hasNext() : boolean; virtual; abstract; { Abstract hasNext() } function next() : TObject; virtual; abstract; { Abstract next() } end; { Doubled linked list } GDLinkedList = class private _size : integer; _serial : integer; _head : GListNode; { Pointer to head of list } _tail : GListnode; { Pointer to tail of list } _owns : boolean; public procedure remove(node : GListNode); overload; published function insertLast(element : pointer) : GListNode; function insertFirst(element : pointer) : GListNode; function insertAfter(tn : GListNode; element : pointer) : GListNode; function insertBefore(tn : GListNode; element : pointer) : GListNode; procedure add(element : TObject); procedure remove(element : TObject); overload; procedure clear(); function size() : integer; function iterator() : GIterator; constructor Create(); destructor Destroy(); override; property head : GListNode read _head; property tail : GListNode read _tail; property ownsObjects : boolean read _owns write _owns; end; { Array to store a set of prime numbers } GPrimes = array of integer; { Array to store a set of linked lists } GDLinkedListArray = array of GDLinkedList; { Definition of hash function } GHASH_FUNC = function(size, prime : integer; const key : string) : integer; { Container for hash elements } GHashValue = class private _key : variant; { Hash key } _refcount : integer; { Reference count } _value : TObject; { Element } published constructor Create(key : variant; value : TObject); procedure addRef(); procedure release(); property key : variant read _key; property refcount : integer read _refcount; property value : TObject read _value; end; { Hash table class, loosely based on the Java2 hashing classes } GHashTable = class private _owns : boolean; hashprime : integer; hashsize : integer; { Size of hash table } bucketList : GDLinkedListArray; { Array of double linked lists } hashFunc : GHASH_FUNC; function getBucket(index : integer) : GDLinkedList; function _get(key : variant) : GHashValue; function findPrimes(n : integer) : GPrimes; published procedure clear(); function isEmpty() : boolean; function size() : integer; function iterator() : GIterator; function get(key : variant) : TObject; procedure put(key : variant; value : TObject); procedure remove(key : variant); function getHash(key : variant) : integer; procedure setHashFunc(func : GHASH_FUNC); procedure hashStats(); virtual; constructor Create(size : integer); destructor Destroy(); override; property ownsObjects : boolean read _owns write _owns; public property item[key : variant] : TObject read get write put; default; { Provides overloaded access to hash table } property buckets[index : integer] : GDLinkedList read getBucket; property bucketcount : integer read hashsize; end; { Singleton class } GSingleton = class public class function NewInstance : TObject; override; procedure FreeInstance; override; { Actual constructor, override this one instead of TObject.Create() } constructor actualCreate(); virtual; { Actual destructor, override this one instead of TObject.Destroy() } destructor actualDestroy(); virtual; end; {$M-} var { Global string hash table } str_hash : GHashTable; function hash_string(const src : string) : PString; overload; function hash_string(src : PString) : PString; overload; procedure unhash_string(var src : PString); function md5Hash(size, prime : integer; const key : string) : integer; function defaultHash(size, prime : integer; const key : string) : integer; function firstHash(size, prime : integer; const key : string) : integer; function sortedHash(size, prime : integer; const key : string) : integer; implementation uses md5; type { Iterator class for double linked lists } GDLinkedListIterator = class(GIterator) private current : GListNode; published constructor Create(list : GDLinkedList); function getCurrent() : TObject; override; function hasNext() : boolean; override; function next() : TObject; override; end; { Iterator class for hash tables } GHashTableIterator = class(GIterator) private tbl : GHashTable; cursor : integer; current : GListNode; published constructor Create(table : GHashTable); function getCurrent() : TObject; override; function hasNext() : boolean; override; function next() : TObject; override; end; { Singleton info class } GSingletonInfo = class private _instanceType : TClass; _instance : TObject; _refcount : integer; published property instanceType : TClass read _instanceType write _instanceType; property instance : TObject read _instance write _instance; property refcount : integer read _refcount write _refcount; end; { Singleton manager class } GSingletonManager = class private infoList : TList; public constructor Create(); destructor Destroy(); override; published procedure addInstance(instance : TObject); function findInstance(classType : TClass) : TObject; function removeInstance(classType : TClass) : boolean; end; { Size of global string hash table } const STR_HASH_SIZE = 1024; var { Instance of singleton manager } singletonManager : GSingletonManager; { Summary: GString constructor } constructor GString.Create(const value : string); begin inherited Create(); _value := value; end; { Summary: GInteger constructor } constructor GInteger.Create(const value : integer); begin inherited Create(); _value := value; end; { Summary: GBitVector constructor } constructor GBitVector.Create(const value : cardinal); begin inherited Create(); _value := value; end; { Summary: Check wether bit is set } function GBitVector.isBitSet(const bit : cardinal) : boolean; begin Result := ((_value and bit) = bit); end; { Summary: Set bit } procedure GBitVector.setBit(const bit : cardinal); begin _value := _value or bit; end; { Summary: Un-set (remove) bit } procedure GBitVector.removeBit(const bit : cardinal); begin if (isBitSet(bit)) then dec(_value, bit); end; { Summary: GListNode constructor } constructor GListNode.Create(e : pointer; p, n : GListNode); begin inherited Create; element := e; next := n; prev := p; end; { Summary: GDLinkedListIterator constructor } constructor GDLinkedListIterator.Create(list : GDLinkedList); begin inherited Create(); current := list.head; end; { Summary: Get current element in list } function GDLinkedListIterator.getCurrent() : TObject; begin if (current <> nil) then Result := current.element else Result := nil; end; { Summary: Check availability of next element } function GDLinkedListIterator.hasNext() : boolean; begin Result := (current <> nil); end; { Summary: Get next element in list (if available) } function GDLinkedListIterator.next() : TObject; begin Result := nil; if (hasNext()) then begin Result := current.element; current := current.next; end; end; { Summary: GHashTableIterator constructor } constructor GHashTableIterator.Create(table : GHashTable); begin inherited Create(); tbl := table; current := nil; cursor := 0; while (current = nil) and (cursor < tbl.hashSize) do begin if (tbl.bucketlist[cursor].head <> nil) then begin current := tbl.bucketList[cursor].head; break; end; inc(cursor); end; end; { Summary: Get current element in list } function GHashTableIterator.getCurrent() : TObject; begin Result := GHashValue(current.element).value; end; { Summary: Check availability of next element } function GHashTableIterator.hasNext() : boolean; begin Result := (current <> nil); end; { Summary: Get next element in list (if available) } function GHashTableIterator.next() : TObject; begin Result := nil; if (hasNext()) then begin Result := GHashValue(current.element).value; current := current.next; if (current = nil) then begin inc(cursor); while (current = nil) and (cursor < tbl.hashSize) do begin if (tbl.bucketlist[cursor].head <> nil) then begin current := tbl.bucketList[cursor].head; break; end; inc(cursor); end; end; end; end; { Summary: GDLinkedList constructor } constructor GDLinkedList.Create(); begin inherited Create(); _owns := true; _head := nil; _tail := nil; _size := 0; _serial := 1; end; { Summary: GDLinkedList destructor } destructor GDLinkedList.Destroy(); begin inherited Destroy(); end; { Summary: Add element to tail of list } function GDLinkedList.insertLast(element : pointer) : GListNode; var node : GListNode; begin node := GListNode.Create(element, _tail, nil); if (_head = nil) then _head := node else _tail.next := node; _tail := node; Result := node; inc(_size); inc(_serial); end; { Summary: Add element to tail of list } function GDLinkedList.insertFirst(element : pointer) : GListNode; var node : GListNode; begin node := GListNode.Create(element, nil, _head); if (_head <> nil) then _head.prev := node; _head := node; Result := node; inc(_size); inc(_serial); end; { Summary: Short for insertLast() } procedure GDLinkedList.add(element : TObject); begin insertLast(element); end; { Summary: Add element after another element } function GDLinkedList.insertAfter(tn : GListNode; element : pointer) : GListNode; var node : GListNode; begin node := GListNode.Create(element, tn, tn.next); if (tn.next <> nil) then tn.next.prev := node; tn.next := node; if (_tail = tn) then _tail := node; Result := node; inc(_size); inc(_serial); end; { Summary: Add element before another element } function GDLinkedList.insertBefore(tn : GListNode; element : pointer) : GListNode; var node : GListNode; begin node := GListNode.Create(element, tn.prev, tn); if (tn.prev <> nil) then tn.prev.next := node; tn.prev := node; if (_head = tn) then _head := node; Result := node; inc(_size); inc(_serial); end; { Summary: Find and remove element from list } procedure GDLinkedList.remove(element : TObject); var node : GListNode; begin node := head; while (node <> nil) do begin if (node.element = element) then begin remove(node); exit; end; node := node.next; end; end; { Summary: Remove node from list } procedure GDLinkedList.remove(node : GListNode); begin if (node.prev = nil) then _head := node.next else node.prev.next := node.next; if (node.next = nil) then _tail := node.prev else node.next.prev := node.prev; dec(_size); inc(_serial); node.Free(); end; { Summary: Get size of list } function GDLinkedList.size() : integer; begin Result := _size; end; { Summary: Remove all items from list, if ownsObject is true items are freed as well } procedure GDLinkedList.clear(); var node : GListNode; begin while (true) do begin node := _tail; if (node = nil) then exit; if (ownsObjects) then TObject(node.element).Free; remove(node); end; end; { Summary: Get iterator for the list } function GDLinkedList.iterator() : GIterator; begin Result := GDLinkedListIterator.Create(Self); end; { Summary: MD5 hashing function } function md5Hash(size, prime : integer; const key : string) : integer; var md : MD5Digest; val : integer; i : integer; begin md := MD5String(key); val := 0; for i := 0 to 7 do val := val + (md[i] shl i); Result := abs(val) mod size; end; { Summary: Default (string) hashing function } function defaultHash(size, prime : integer; const key : string) : integer; var i : integer; val : integer; begin val := 0; {$Q-} for i := 1 to length(key) do val := val * prime + byte(key[i]); Result := abs(val) mod size; end; { Summary: Alternative string hashing function, only uses first character in string } function firstHash(size, prime : integer; const key : string) : integer; begin if (length(key) >= 1) then Result := (byte(key[1]) * prime) mod size else Result := 0; end; { Summary: Alternative string hashing function, sorts linearly on first character in string } function sortedHash(size, prime : integer; const key : string) : integer; begin if (length(key) >= 1) then Result := (byte(key[1]) * size) div 256 else Result := 0; end; { Summary: Get an array of prime numbers } function GHashTable.findPrimes(n : integer) : GPrimes; var i, j : integer; limit : double; numbers : GPrimes; numberpool : array of boolean; begin setlength(numberpool, n); for i := 2 to n - 1 do numberpool[i] := true; limit := sqrt(n); j := 2; i := j + j; while (i < n) do begin numberpool[i] := false; i := i + j; end; j := 3; while (j <= limit) do begin if (numberpool[j] = true) then begin i := j + j; while (i < n) do begin numberpool[i] := false; i := i + j; end; end; j := j + 2; end; j := 0; for i := 0 to n - 1 do begin if (numberpool[i]) then begin setLength(numbers, j + 1); numbers[j] := i; j := j + 1; end; end; Result := numbers; end; { Summary: GHashValue constructor } constructor GHashValue.Create(key : variant; value : TObject); begin inherited Create(); _key := key; _value := value; _refcount := 1; end; { Summary: Increases the reference count by one } procedure GHashValue.addRef(); begin inc(_refcount); end; { Summary: Decreases the reference count by one } procedure GHashValue.release(); begin dec(_refcount); end; { Summary: Get hash-value for a key Remarks: Uses static hash function for integers } function GHashTable.getHash(key : variant) : integer; {$O-} begin if (varType(key) = varString) then Result := hashFunc(hashsize, hashprime, key) else if (varType(key) in [varSmallint,varInteger,varShortInt,varByte,varWord,varLongWord]) then Result := (integer(key) * hashprime) mod hashsize else { shouldn't be here } raise Exception.Create('Impossible to determine hashkey for unknown variant type ' + VarTypeAsText(VarType(key))); // final safeguard against indices < 0 Result := abs(Result); end; { Summary: Set hash function } procedure GHashTable.setHashFunc(func : GHASH_FUNC); begin hashFunc := func; end; { Summary: Retrieves bucket at given index } function GHashTable.getBucket(index : integer) : GDLinkedList; begin if (index < 0) or (index >= length(bucketList)) then begin raise Exception.Create('Index (' + IntToStr(index) + ') out of bounds'); end else Result := bucketList[index]; end; { Summary: Get hash object corresponding with key } function GHashTable._get(key : variant) : GHashValue; var hash : integer; node : GListNode; begin Result := nil; hash := getHash(key); try node := bucketList[hash].head; except node := nil; end; while (node <> nil) do begin if (GHashValue(node.element).key = key) then begin Result := GHashValue(node.element); break; end; node := node.next; end; end; { Summary: Get element corresponding with key } function GHashTable.get(key : variant) : TObject; var hv : GHashValue; begin Result := nil; hv := _get(key); if (hv <> nil) then Result := hv.value; end; { Summary: Put element in hash table } procedure GHashTable.put(key : variant; value : TObject); var hash : integer; hv : GHashValue; begin if (value = nil) then begin remove(key); end else begin hv := _get(key); if (hv <> nil) then begin hv.addRef(); end else begin hash := getHash(key); hv := GHashValue.Create(key, value); bucketList[hash].insertLast(hv); end; end; end; { Summary: Remove key from hash table } procedure GHashTable.remove(key : variant); var hash : integer; fnode, node : GListNode; begin fnode := nil; hash := getHash(key); node := bucketList[hash].head; while (node <> nil) do begin if (GHashValue(node.element).key = key) then begin fnode := node; break; end; node := node.next; end; if (fnode <> nil) then bucketList[hash].remove(fnode); end; { Summary: Get size of hash table } function GHashTable.size() : integer; var i : integer; total : integer; begin total := 0; for i := 0 to hashsize - 1 do begin total := total + bucketList[i].size(); end; Result := total; end; { Summary: Check if hash table is empty } function GHashTable.isEmpty() : boolean; begin Result := size() = 0; end; { Summary: Display hash table statistics } procedure GHashTable.hashStats(); var i : integer; total : integer; load : single; min, max : integer; begin total := 0; min := 65536; max := 0; for i := 0 to hashsize - 1 do begin total := total + bucketList[i].size(); if (bucketList[i].size() < min) then min := bucketList[i].size(); if (bucketList[i].size() > max) then max := bucketList[i].size(); end; load := total / hashsize; writeln('Hash size ' + inttostr(hashsize) + ' with key ' + inttostr(hashprime)); writeln('Total hash items : ' + inttostr(total)); writeln('Load factor : ' + floattostrf(load, ffFixed, 7, 4)); end; { Summary: Remove all items from hash table (See GDLinkedList.clear()) } procedure GHashTable.clear(); var i : integer; begin for i := 0 to hashsize - 1 do begin bucketList[i].ownsObjects := ownsObjects; bucketList[i].clear(); end; end; { Summary: GHashTable constructor } constructor GHashTable.Create(size : integer); var n : integer; primes : GPrimes; begin inherited Create(); primes := findPrimes(size + 32); randomize; hashsize := primes[length(primes) - 1]; hashprime := primes[random(length(primes))]; setlength(bucketList, hashsize); for n := 0 to hashsize - 1 do bucketList[n] := GDLinkedList.Create; hashFunc := defaultHash; _owns := true; end; { Summary: GHashTable destructor } destructor GHashTable.Destroy(); var n : integer; begin clear(); for n := 0 to hashsize - 1 do bucketList[n].Free; setlength(bucketList, 0); inherited Destroy(); end; { Summary: Get iterator for the hash table } function GHashTable.iterator() : GIterator; begin Result := GHashTableIterator.Create(Self); end; { Summary: Add string to global string hash table } function hash_string(const src : string) : PString; var hv : GHashValue; g : GString; begin hv := str_hash._get(src); if (hv <> nil) then begin Result := @GString(hv.value).value; hv.addRef(); end else begin g := GString.Create(src); str_hash.put(src, g); Result := @g.value; end; end; { Summary: Add string to global string hash table } function hash_string(src : PString) : PString; var hv : GHashValue; g : GString; begin hv := str_hash._get(src^); if (hv <> nil) then begin Result := @GString(hv.value).value; hv.addRef(); end else begin g := GString.Create(src^); str_hash.put(src^, g); Result := @g.value; end; end; { Summary: Remove string from global string hash table } procedure unhash_string(var src : PString); var hv : GHashValue; begin if (src = nil) then exit; hv := str_hash._get(src^); if (hv <> nil) then begin hv.release(); if (hv.refcount <= 0) then begin str_hash.remove(src^); hv.value.Free(); hv.Free(); end; end; src := nil; end; { Summary: GSingletonManager constructor } constructor GSingletonManager.Create(); begin inherited Create(); infoList := TList.Create(); end; { Summary: GSingletonManager destructor } destructor GSingletonManager.Destroy(); var x : integer; begin if (infoList.Count > 0) then begin for x := 0 to infoList.Count - 1 do GSingletonInfo(infoList[x]).Free(); end; FreeAndNil(infoList); inherited Destroy(); end; { Summary: Adds instance to manager } procedure GSingletonManager.addInstance(instance : TObject); var info : GSingletonInfo; begin info := GSingletonInfo.Create(); info.instance := instance; info.instanceType := instance.ClassType; info.refcount := 1; infoList.add(info); end; { Summary: Finds instance of classtype, or nil if unsuccessful } function GSingletonManager.findInstance(classType : TClass) : TObject; var index : integer; info : GSingletonInfo; begin Result := nil; for index := 0 to infoList.Count - 1 do begin info := GSingletonInfo(infoList[index]); if (info.instanceType = classType) then begin Result := info.instance; info.refcount := info.refcount + 1; exit; end; end; end; { Summary: Lowers refcount of classtype, removes if 0 } function GSingletonManager.removeInstance(classType : TClass) : boolean; var index : integer; info : GSingletonInfo; begin Result := false; for index := 0 to infoList.Count - 1 do begin info := GSingletonInfo(infoList[index]); if (info.instanceType = classType) and (info.refcount > 0) then begin info.refcount := info.refcount - 1; if (info.refcount = 0) then Result := true; exit; end; end; end; { Summary: Returns (if it exists) a previous instance or a new instance } class function GSingleton.NewInstance : TObject; begin Result := singletonManager.findInstance(Self); if (not Assigned(Result)) then begin Result := inherited NewInstance; GSingleton(Result).actualCreate(); singletonManager.addInstance(Result); end; end; { Summary: Frees the instance } procedure GSingleton.FreeInstance; begin if (singletonManager.removeInstance(Self.ClassType)) then begin GSingleton(Self).actualDestroy(); inherited FreeInstance; end; end; { Summary: Default GSingleton constructor } constructor GSingleton.actualCreate(); begin end; { Summary: Default GSingleton destructor } destructor GSingleton.actualDestroy(); begin end; initialization str_hash := GHashTable.Create(STR_HASH_SIZE); singletonManager := GSingletonManager.Create(); finalization FreeAndNil(singletonManager); FreeAndNil(str_hash); end.