Contributor: ALIN FLAIDER

unit Collect;
{ Collection classes for Delphi 2.0
  Alin Flaider, 1996
  aflaidar@datalog.ro }
  
interface
uses Windows, Classes, Sysutils;

const
  coIndexError = -1;              { Index out of range }
  coOverflow   = -2;              { Overflow }
  coUnderflow  = -3;              { Underflow }

type
 CollException = class(Exception);

 TCollection = class( TObject)
    private                       { return item at index position }
       function    At( Index : integer) : Pointer;
                                  { replace item at index position}
       procedure   AtPut( Index : integer; Item : Pointer);
    protected
       It     : PPointerList;     { array of pointers }
       Limit  : integer;          { Current Allocated size of array}
       Delta  : integer;          {Number of items by which the collection grows when full}
                                  { deletes item at index position }
       procedure   AtDelete      (Index : integer);
                                  { generates CollException }
       procedure   Error         (Code,Info : Integer); virtual;
                                  { destroys specified Item; override this method if Item is not
                                    a descendant of TObject }
       procedure   FreeItem      (Item : Pointer); virtual;
    public
       Count  : integer;          {Current Number of Items}
       constructor create(aLimit, aDelta : integer);
                                  {before deallocating object it disposes all items and the storage array}
       destructor  destroy; override;
                                  {inserts Item at specified position }
       procedure   AtInsert( Index : integer; Item : Pointer);
                                  {deletes and disposes Item at specified position}
       procedure   AtFree(Index: Integer);
                                  {deletes Item}
       procedure   Delete( Item : Pointer);
                                  {deletes all Items without disposing them }
       procedure   DeleteAll;
                                  {formerly Free, renamed to Clear to avoid bypassing inherited TObject.Free;
                                   deletes and disposes Item }
       procedure Clear(Item: Pointer);
                                  {finds first item that satisfies condition specified in
                                   function Test( Item: pointer): boolean}
       function    FirstThat( Test : Pointer) : Pointer;
                                  {finds last item that satisfies condition specified in
                                   function Test( Item: pointer): boolean}
       function    LastThat( Test : Pointer) : Pointer;
                                  {calls procedure Action( Item: pointer) for each item in collection}
       procedure   ForEach( Action : Pointer);
                                  {disposes all items; set counter to zero}
       procedure   FreeAll;
                                  {finds position of Item using a linear search}
       function    IndexOf( Item : Pointer) : integer; virtual;
                                  {inserts Item at the end of collection}
       procedure   Insert( Item : Pointer); virtual;
                                  {packs collection by removing nil Items}
       procedure   Pack;
                                  {expands array of pointers }
       procedure   SetLimit( aLimit : integer);virtual;
                                  {direct access to items through position}
       property Items[Index: integer]: pointer read At write AtPut; default;
    end;

    TSortedCollection = class(TCollection)
       Duplicates: boolean;       {if true, rejects item whose key already exists}
                                  {override this method to specify relation bewtween two keys
                                  1 if Key1 comes after Key2, -1 if Key1 comes before Key2,
                                  0 if Key1 is equivalent to Key2}
       function Compare (Key1,Key2 : Pointer): Integer; virtual; abstract;
                                  {returns key of Item}
       function KeyOf   (Item : Pointer): Pointer; virtual;
                                  {finds index of item by calling Search}
       function IndexOf (Item : Pointer): integer; virtual;
                                  {finds item required position and performs insertion }
       procedure Insert  (Item : Pointer); virtual;
                                  {finds index of item by performing an optimised search}
       function Search  (key : Pointer; Var Index : integer) : Boolean; virtual;
    end;

implementation

constructor TCollection.Create(ALimit, ADelta: Integer);
begin
   inherited Create;
   Limit:= 0;
   Delta:=aDelta;
   Count:=0;
   It := nil;
   SetLimit( ALimit);
end;

destructor TCollection.Destroy;
begin
   FreeAll;
   SetLimit(0);
   inherited Destroy;
end;

function TCollection.At(Index: Integer): Pointer;
begin
   If Index > pred(Count) then
   begin
     Error(coIndexError,0);
     Result :=nil;
   end
   else Result := It^[Index];
end;

procedure TCollection.AtPut(Index: Integer; Item: Pointer);
begin
   if (Index < 0) or (Index >= Count) then
     Error(coIndexError,0)
   else It^[Index] := Item;
end;

procedure TCollection.AtDelete(Index: Integer);
var p: pointer;
begin
   if (Index < 0) or (Index >= Count) then
   begin
      Error(coIndexError,0);
      exit;
   end;
   if Index < pred(Count) then
     move( It^[succ(Index)], It^[Index], (count-index)*sizeof(pointer));
   Dec(Count);
end;

procedure TCollection.AtInsert( Index: integer; Item: pointer);
var i : integer;
begin
   if (Index < 0) or ( Index > Count) then
   begin
      Error(coIndexError,0);
      exit;
   end;
   if Limit = Count then
   begin
     if Delta = 0 then
     begin
        Error(coOverFlow,0);
        exit;
     end;
     SetLimit( Limit+Delta);
   end;
   If Index <> Count then  {move compensates for overlaps}
      move( It^[Index], It^[Index+1], (count - index)*sizeof(pointer));
   It^[Index] := Item;
   Inc(Count);
end;

procedure TCollection.Delete( Item: pointer);
begin
   AtDelete(Indexof(Item));
end;

procedure TCollection.DeleteAll;
begin
   Count:=0
end;

procedure TCollection.Error(Code, Info: Integer);
begin
   case Code of
        coIndexError: raise CollException.Create('Collection error; wrong index: '+IntToStr(Info));
        coOverflow:  raise CollException.Create('Collection overflow - cannot grow!');
        coUnderflow: raise CollException.Create('Collection underflow - cannot shrink!');
   end
end;

function TCollection.FirstThat(Test: Pointer): Pointer;
type
   tTestFunc = function( p : pointer) : Boolean;
var i : integer;
begin
  Result := nil;
  for i := 0 to pred(count) do
    if tTestFunc(test)(It^[i]) then begin
       Result := It[i];
       break
    end
end;

procedure TCollection.ForEach(Action: Pointer);
type
   tActionProc = procedure(p : pointer);
var i : integer;
begin
  for i := 0 to pred(Count) do
    tActionProc(Action)(It^[i]);
end;

procedure TCollection.Clear(Item: Pointer);
begin
   Delete(Item);
   FreeItem(Item);
end;

procedure TCollection.FreeAll;
var i : integer;
begin
  for I := 0 to Count - 1 do FreeItem(At(I));
  Count := 0;
end;

procedure TCollection.FreeItem(Item: Pointer);
begin
  if Item <> nil then TObject(Item).Free;
end;

function TCollection.IndexOf(Item: Pointer): integer;
var i : integer;
begin
  Result := -1;
  for i := 0 to pred(count) do
    if Item = It^[i] then begin
       Result := i;
       break
    end
end;

procedure TCollection.Insert(Item: Pointer);
begin
   AtInsert(Count,Item);
end;

function TCollection.LastThat(Test: Pointer): pointer;
type
   tTestFunc = function( p : pointer) : Boolean;
var i : integer;
begin
  Result := nil;
  for i := pred(count) downto 1 do
    if tTestFunc(test)(It^[i]) then begin
       Result := It^[i];
       break
    end
end;

procedure TCollection.Pack;
var i: integer;
begin
  for i := pred(count) downto 0 do if It^[i] = nil then AtDelete(i);
end;

procedure TCollection.SetLimit(ALimit: Integer);
begin
  if (ALimit < Count) then Error( coUnderFlow , 0);
  if ALimit <> Limit then
  begin
    ReallocMem( It, ALimit* SizeOf(Pointer));
    Limit := ALimit;
  end;
end;

function TSortedCollection.IndexOf(Item: Pointer): Integer;
var
  i: Integer;
begin
  IndexOf := -1;
  if Search(KeyOf(Item), i) then
  begin
    if Duplicates then
      while (i < Count) and (Item <> It^[I]) do Inc(i);
    if i < Count then IndexOf := i;
  end;
end;

procedure TSortedCollection.Insert(Item: Pointer);
var i : integer;
begin
  if not Search(KeyOf(Item), I) or Duplicates then AtInsert(I, Item);
end;

function TSortedCollection.KeyOf(Item: Pointer): Pointer;
begin
  Result := Item;
end;

function TSortedCollection.Search;
var
  L, H, I, C: Integer;
begin
  Search := False;
  L := 0;
  H := Count - 1;
  while L <= H do
  begin
    I := (L + H) shr 1;
    C := Compare(KeyOf(It^[I]), Key);
    if C < 0 then L := I + 1 else
    begin
      H := I - 1;
      if C = 0 then
      begin
        Search := True;
        if not Duplicates then L := I;
      end;
    end;
  end;
  Index := L;
end;

procedure TCollection.AtFree(Index: Integer);
var
  Item: Pointer;
begin
  Item := At(Index);
  AtDelete(Index);
  FreeItem(Item);
end;

end.