{-----------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/MPL-1.1.html

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

The Original Code is: JvObjStr.PAS, released on 2002-07-04.

The Initial Developers of the Original Code are: Fedor Koshevnikov, Igor Pavluk and Serge Korolev
Copyright (c) 1997, 1998 Fedor Koshevnikov, Igor Pavluk and Serge Korolev
Copyright (c) 2001,2002 SGB Software
All Rights Reserved.

Last Modified: 2002-07-04

You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.sourceforge.net

Known Issues:
-----------------------------------------------------------------------------}

{$I JVCL.INC}

unit JvObjStr;

interface

uses
  {$IFDEF COMPILER6_UP}
  SysUtils, Classes, RTLConsts;
  {$ELSE}
  SysUtils, Classes;
  {$ENDIF}

type
  TDestroyEvent = procedure(Sender, AObject: TObject) of object;
  TObjectSortCompare = function(const S1, S2: string;
    Item1, Item2: TObject): Integer of object;

  TJvObjectStrings = class(TStringList)
  private
    FOnDestroyObject: TDestroyEvent;
  protected
    procedure DestroyObject(AObject: TObject); virtual;
    procedure PutObject(Index: Integer; AObject: TObject); override;
  public
    procedure Clear; override;
    procedure Delete(Index: Integer); override;
    procedure Move(CurIndex, NewIndex: Integer); override;
    procedure Remove(Index: Integer);
    procedure ParseStrings(const Values: string);
    procedure SortList(Compare: TObjectSortCompare);
    property OnDestroyObject: TDestroyEvent read FOnDestroyObject write FOnDestroyObject;
  end;

const
  {$IFDEF WIN32}
  MaxHugeListSize = MaxListSize;
  {$ELSE}
  MaxHugeListSize = (MaxLongint div SizeOf(Pointer)) - 4;
  {$ENDIF}

type
  {$IFDEF WIN32}
  TJvHugeList = class(TList);
  {$ELSE}
  TJvHugeList = class(TObject)
  private
    FList: TMemoryStream;
    FCount: Longint;
    FCapacity: Longint;
  protected
    function Get(Index: Longint): Pointer;
    procedure Grow; virtual;
    procedure Put(Index: Longint; Item: Pointer);
    procedure SetCapacity(NewCapacity: Longint);
    procedure SetCount(NewCount: Longint);
  public
    destructor Destroy; override;
    function Add(Item: Pointer): Longint;
    procedure Clear;
    procedure Delete(Index: Longint);
    procedure Exchange(Index1, Index2: Longint);
    function Expand: TJvHugeList;
    function First: Pointer;
    function IndexOf(Item: Pointer): Longint;
    procedure Insert(Index: Longint; Item: Pointer);
    function Last: Pointer;
    procedure Move(CurIndex, NewIndex: Longint);
    function Remove(Item: Pointer): Longint;
    procedure Pack;
    property Capacity: Longint read FCapacity write SetCapacity;
    property Count: Longint read FCount write SetCount;
    property Items[Index: Longint]: Pointer read Get write Put; default;
  end;
  {$ENDIF WIN32}

{$IFDEF WIN32}

type
  TItemSortCompare = function(Item1, Item2: TCollectionItem): Integer of object;

  TJvSortCollection = class(TCollection)
  protected
    procedure QuickSort(L, R: Integer; Compare: TItemSortCompare); virtual;
  public
    procedure Sort(Compare: TItemSortCompare);
  end;

{$ENDIF WIN32}

implementation

uses
  Consts,
  {$IFNDEF WIN32}
  JvVCLUtils,
  {$ENDIF}
  JvStrUtils;

// (rom) to JCL

procedure QuickSort(SortList: TStrings; L, R: Integer; SCompare: TObjectSortCompare);
var
  I, J: Integer;
  P: TObject;
  S: string;
begin
  repeat
    I := L;
    J := R;
    P := SortList.Objects[(L + R) shr 1];
    S := SortList[(L + R) shr 1];
    repeat
      while SCompare(SortList[I], S, SortList.Objects[I], P) < 0 do
        Inc(I);
      while SCompare(SortList[J], S, SortList.Objects[J], P) > 0 do
        Dec(J);
      if I <= J then
      begin
        SortList.Exchange(I, J);
        Inc(I);
        Dec(J);
      end;
    until I > J;
    if L < J then
      QuickSort(SortList, L, J, SCompare);
    L := I;
  until I >= R;
end;

//=== TJvObjectStrings =======================================================

procedure TJvObjectStrings.DestroyObject(AObject: TObject);
begin
  if Assigned(FOnDestroyObject) then
    FOnDestroyObject(Self, AObject)
  else
  if AObject <> nil then
    AObject.Free;
end;

procedure TJvObjectStrings.Clear;
var
  I: Integer;
begin
  if Count > 0 then
  begin
    Changing;
    for I := 0 to Count - 1 do
      Objects[I] := nil;
    BeginUpdate;
    try
      inherited Clear;
    finally
      EndUpdate;
    end;
    Changed;
  end;
end;

procedure TJvObjectStrings.Delete(Index: Integer);
begin
  Objects[Index] := nil;
  inherited Delete(Index);
end;

procedure TJvObjectStrings.Remove(Index: Integer);
begin
  inherited Delete(Index);
end;

procedure TJvObjectStrings.Move(CurIndex, NewIndex: Integer);
var
  TempObject: TObject;
  TempString: string;
begin
  if CurIndex <> NewIndex then
  begin
    TempString := Get(CurIndex);
    TempObject := GetObject(CurIndex);
    inherited Delete(CurIndex);
    try
      InsertObject(NewIndex, TempString, TempObject);
    except
      DestroyObject(TempObject);
      raise;
    end;
  end;
end;

procedure TJvObjectStrings.PutObject(Index: Integer; AObject: TObject);
begin
  Changing;
  BeginUpdate;
  try
    if (Index < Self.Count) and (Index >= 0) then
      DestroyObject(Objects[Index]);
    inherited PutObject(Index, AObject);
  finally
    EndUpdate;
  end;
  Changed;
end;

procedure TJvObjectStrings.ParseStrings(const Values: string);
var
  Pos: Integer;
begin
  Pos := 1;
  BeginUpdate;
  try
    while Pos <= Length(Values) do
      Add(ExtractSubstr(Values, Pos, [';']));
  finally
    EndUpdate;
  end;
end;

procedure TJvObjectStrings.SortList(Compare: TObjectSortCompare);
begin
  if Sorted then
    {$IFDEF COMPILER3_UP}
    Error(SSortedListError, 0);
    {$ELSE}
    raise EListError.Create(LoadStr(SSortedListError));
    {$ENDIF}
  if Count > 0 then
  begin
    BeginUpdate;
    try
      QuickSort(Self, 0, Count - 1, Compare);
    finally
      EndUpdate;
    end;
  end;
end;

//=== TJvHugeList ============================================================

{$IFNDEF WIN32}

function ReturnAddr: Pointer; assembler;
asm
        MOV     AX,[BP].Word[2]
        MOV     DX,[BP].Word[4]
end;

procedure ListError(Index: Longint);
begin
  raise EListError.Create(LoadStr(SListIndexError) +
    Format(' (%d)', [Index]))at ReturnAddr;
end;

destructor TJvHugeList.Destroy;
begin
  Clear;
end;

function TJvHugeList.Add(Item: Pointer): Longint;
begin
  Result := FCount;
  if Result = FCapacity then
    Grow;
  FList.Position := Result * SizeOf(Pointer);
  FList.WriteBuffer(Item, SizeOf(Pointer));
  Inc(FCount);
end;

procedure TJvHugeList.Clear;
begin
  SetCount(0);
  SetCapacity(0);
end;

procedure TJvHugeList.Delete(Index: Longint);
begin
  if (Index < 0) or (Index >= FCount) then
    ListError(Index);
  Dec(FCount);
  if Index < FCount then
    HugeMove(FList.Memory, Index, Index + 1, FCount - Index);
end;

function TJvHugeList.Get(Index: Longint): Pointer;
begin
  if (Index < 0) or (Index >= FCount) then
    ListError(Index);
  FList.Position := Index * SizeOf(Pointer);
  FList.ReadBuffer(Result, SizeOf(Pointer));
end;

procedure TJvHugeList.Put(Index: Longint; Item: Pointer);
begin
  if (Index < 0) or (Index >= FCount) then
    ListError(Index);
  FList.Position := Index * SizeOf(Pointer);
  FList.WriteBuffer(Item, SizeOf(Pointer));
end;

procedure TJvHugeList.Exchange(Index1, Index2: Longint);
var
  Item: Pointer;
begin
  Item := Get(Index1);
  Put(Index1, Get(Index2));
  Put(Index2, Item);
end;

function TJvHugeList.Expand: TJvHugeList;
begin
  if FCount = FCapacity then
    Grow;
  Result := Self;
end;

function TJvHugeList.First: Pointer;
begin
  Result := Get(0);
end;

procedure TJvHugeList.Grow;
var
  Delta: Longint;
begin
  // (rom) maybe some levels more here
  if FCapacity > 8 then
    Delta := 16
  else
  if FCapacity > 4 then
    Delta := 8
  else
    Delta := 4;
  SetCapacity(FCapacity + Delta);
end;

function TJvHugeList.IndexOf(Item: Pointer): Longint;
begin
  Result := 0;
  while (Result < FCount) and (Get(Result) <> Item) do
    Inc(Result);
  if Result = FCount then
    Result := -1;
end;

procedure TJvHugeList.Insert(Index: Longint; Item: Pointer);
begin
  if (Index < 0) or (Index > FCount) then
    ListError(Index);
  if FCount = FCapacity then
    Grow;
  if Index < FCount then
    HugeMove(FList.Memory, Index + 1, Index, FCount - Index);
  FList.Position := Index * SizeOf(Pointer);
  FList.WriteBuffer(Item, SizeOf(Pointer));
  Inc(FCount);
end;

function TJvHugeList.Last: Pointer;
begin
  Result := Get(FCount - 1);
end;

procedure TJvHugeList.Move(CurIndex, NewIndex: Longint);
var
  Item: Pointer;
begin
  if CurIndex <> NewIndex then
  begin
    if (NewIndex < 0) or (NewIndex >= FCount) then
      ListError(NewIndex);
    Item := Get(CurIndex);
    Delete(CurIndex);
    Insert(NewIndex, Item);
  end;
end;

function TJvHugeList.Remove(Item: Pointer): Longint;
begin
  Result := IndexOf(Item);
  if Result <> -1 then
    Delete(Result);
end;

procedure TJvHugeList.Pack;
var
  I: Longint;
begin
  for I := FCount - 1 downto 0 do
    if Items[I] = nil then
      Delete(I);
end;

procedure TJvHugeList.SetCapacity(NewCapacity: Longint);
var
  NewList: TMemoryStream;
begin
  if (NewCapacity < FCount) or (NewCapacity > MaxHugeListSize) then
    ListError(NewCapacity);
  if NewCapacity <> FCapacity then
  begin
    if NewCapacity = 0 then
      NewList := nil
    else
    begin
      NewList := TMemoryStream.Create;
      NewList.SetSize(NewCapacity * SizeOf(Pointer));
      if FCount <> 0 then
      begin
        FList.Position := 0;
        FList.ReadBuffer(NewList.Memory^, FCount * SizeOf(Pointer));
      end;
    end;
    if FCapacity <> 0 then
      FList.Free;
    FList := NewList;
    FCapacity := NewCapacity;
  end;
end;

procedure TJvHugeList.SetCount(NewCount: Longint);
begin
  if (NewCount < 0) or (NewCount > MaxHugeListSize) then
    ListError(NewCount);
  if NewCount > FCapacity then
    SetCapacity(NewCount);
  FCount := NewCount;
end;

{$ENDIF}

//=== TJvSortCollection ======================================================

{$IFDEF WIN32}

procedure TJvSortCollection.QuickSort(L, R: Integer; Compare: TItemSortCompare);
var
  I, J: Integer;
  P, P1, P2: TCollectionItem;
begin
  repeat
    I := L;
    J := R;
    P := Items[(L + R) shr 1];
    repeat
      while Compare(Items[I], P) < 0 do
        Inc(I);
      while Compare(Items[J], P) > 0 do
        Dec(J);
      if I <= J then
      begin
        P1 := Items[I];
        P2 := Items[J];
        P1.Index := J;
        P2.Index := I;
        Inc(I);
        Dec(J);
      end;
    until I > J;
    if L < J then
      QuickSort(L, J, Compare);
    L := I;
  until I >= R;
end;

procedure TJvSortCollection.Sort(Compare: TItemSortCompare);
begin
  if Count > 0 then
  begin
    BeginUpdate;
    try
      QuickSort(0, Count - 1, Compare);
    finally
      EndUpdate;
    end;
  end;
end;

{$ENDIF WIN32}

end.

