{**************************************************************************************************}
{                                                                                                  }
{ Project JEDI Code Library (JCL)                                                                  }
{                                                                                                  }
{ The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); }
{ you may not use this file except in compliance with the License. You may obtain a copy of the    }
{ License at http://www.mozilla.org/MPL/                                                           }
{                                                                                                  }
{ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF   }
{ ANY KIND, either express or implied. See the License for the specific language governing rights  }
{ and limitations under the License.                                                               }
{                                                                                                  }
{ The Original Code is NewStringListUnit.pas.                                                      }
{                                                                                                  }
{ The Initial Developer of the Original Code is Romullo Sousa.                                     }
{ Portions created by Romullo Sousa are Copyright (C) Romullo Sousa. All rights reserved.          }
{                                                                                                  }
{ Contributor(s):                                                                                  }
{     Romullo Sousa (romullobr)                                                                    }
{     Leo Simas (Leh_U)                                                                            }
{                                                                                                  }
{**************************************************************************************************}

// Last modified: $Date: 2006-12-30 10:04:59 +0100 (Sa, 30 Dez 2006) $

unit JclStringLists;

{$I jcl.inc}

interface

uses
  {$IFDEF UNITVERSIONING}
  JclUnitVersioning,
  {$ENDIF UNITVERSIONING}
  {$IFDEF MSWINDOWS}
  Windows,
  {$ENDIF MSWINDOWS}
  {$IFDEF HAS_UNIT_VARIANTS}
  Variants,
  {$ENDIF HAS_UNIT_VARIANTS}
  Classes, SysUtils,
  JclBase;

type
  IJclStringList = interface;

  TJclStringListObjectsMode = (omNone, omObjects, omVariants, omInterfaces);

  TJclStringListSortCompare = function(List: IJclStringList; Index1, Index2: Integer): Integer;

  IJclStringList = interface(IInterface)
    ['{8DC5B71C-4756-404D-8636-7872CD299796}']
    { From TStrings/TStringList }
    function Add(const S: string): Integer; overload;
    function AddObject(const S: string; AObject: TObject): Integer;
    function Get(Index: Integer): string;
    function GetCapacity: Integer;
    function GetCount: Integer;
    function GetObjects(Index: Integer): TObject;
    function GetTextStr: string;
    function GetValue(const Name: string): string;
    function Find(const S: string; var Index: Integer): Boolean;
    function IndexOf(const S: string): Integer;
    {$IFDEF COMPILER6_UP}
    function GetCaseSensitive: Boolean;
    {$ENDIF COMPILER6_UP}
    function GetDuplicates: TDuplicates;
    function GetOnChange: TNotifyEvent;
    function GetOnChanging: TNotifyEvent;
    function GetSorted: Boolean;
    function Equals(Strings: TStrings): Boolean;
    function IndexOfName(const Name: string): Integer;
    function IndexOfObject(AObject: TObject): Integer; 
    function LoadFromFile(const FileName: string): IJclStringList;
    function LoadFromStream(Stream: TStream): IJclStringList;
    function SaveToFile(const FileName: string): IJclStringList;
    function SaveToStream(Stream: TStream): IJclStringList;
    function GetCommaText: string;
    {$IFDEF COMPILER6_UP}
    function GetDelimitedText: string;
    function GetDelimiter: Char;
    {$ENDIF COMPILER6_UP}
    function GetName(Index: Integer): string;
    {$IFDEF COMPILER7_UP}
    function GetNameValueSeparator: Char;
    function GetValueFromIndex(Index: Integer): string;
    {$ENDIF COMPILER7_UP}
    {$IFDEF COMPILER6_UP}
    function GetQuoteChar: Char;
    {$ENDIF COMPILER6_UP}
    procedure SetCommaText(const Value: string);
    {$IFDEF COMPILER6_UP}
    procedure SetDelimitedText(const Value: string);
    procedure SetDelimiter(const Value: Char);
    {$ENDIF COMPILER6_UP}
    {$IFDEF COMPILER7_UP}
    procedure SetNameValueSeparator(const Value: Char);
    procedure SetValueFromIndex(Index: Integer; const Value: string);
    {$ENDIF COMPILER7_UP}
    {$IFDEF COMPILER6_UP}
    procedure SetQuoteChar(const Value: Char);
    {$ENDIF COMPILER6_UP}
    procedure AddStrings(Strings: TStrings); overload;
    procedure SetObjects(Index: Integer; const Value: TObject);
    procedure Put(Index: Integer; const S: string);
    procedure SetCapacity(NewCapacity: Integer);
    procedure SetTextStr(const Value: string);
    procedure SetValue(const Name, Value: string);
    {$IFDEF COMPILER6_UP}
    procedure SetCaseSensitive(const Value: Boolean);
    {$ENDIF COMPILER6_UP}
    procedure SetDuplicates(const Value: TDuplicates);
    procedure SetOnChange(const Value: TNotifyEvent);
    procedure SetOnChanging(const Value: TNotifyEvent);
    procedure SetSorted(const Value: Boolean);
    property Count: Integer read GetCount;
    property Strings[Index: Integer]: string read Get write Put; default;
    property Text: string read GetTextStr write SetTextStr;
    property Objects[Index: Integer]: TObject read GetObjects write SetObjects;
    property Capacity: Integer read GetCapacity write SetCapacity;
    property Values[const Name: string]: string read GetValue write SetValue;
    property Duplicates: TDuplicates read GetDuplicates write SetDuplicates;
    property Sorted: Boolean read GetSorted write SetSorted;
    {$IFDEF COMPILER6_UP}
    property CaseSensitive: Boolean read GetCaseSensitive write SetCaseSensitive;
    {$ENDIF COMPILER6_UP}
    property OnChange: TNotifyEvent read GetOnChange write SetOnChange;
    property OnChanging: TNotifyEvent read GetOnChanging write SetOnChanging;
    {$IFDEF COMPILER6_UP}
    property DelimitedText: string read GetDelimitedText write SetDelimitedText;
    property Delimiter: Char read GetDelimiter write SetDelimiter;
    {$ENDIF COMPILER6_UP}
    property Names[Index: Integer]: string read GetName;
    {$IFDEF COMPILER6_UP}
    property QuoteChar: Char read GetQuoteChar write SetQuoteChar;
    {$ENDIF COMPILER6_UP}
    property CommaText: string read GetCommaText write SetCommaText;
    {$IFDEF COMPILER7_UP}
    property ValueFromIndex[Index: Integer]: string read GetValueFromIndex write SetValueFromIndex;
    property NameValueSeparator: Char read GetNameValueSeparator write SetNameValueSeparator;
    {$ENDIF COMPILER7_UP}
    { New }
    function Assign(Source: TPersistent): IJclStringList;
    function LoadExeParams: IJclStringList;
    function Exists(const S: string): Boolean;
    function ExistsName(const S: string): Boolean;
    function DeleteBlanks: IJclStringList;
    function KeepIntegers: IJclStringList;
    function DeleteIntegers: IJclStringList;
    function ReleaseInterfaces: IJclStringList;
    function FreeObjects(AFreeAndNil: Boolean = False): IJclStringList;
    function Clone: IJclStringList;
    function Insert(Index: Integer; const S: string): IJclStringList;
    function InsertObject(Index: Integer; const S: string; AObject: TObject): IJclStringList;
    function Sort(ACompareFunction: TJclStringListSortCompare = nil): IJclStringList;
    function SortAsInteger: IJclStringList;
    function SortByName: IJclStringList;
    function Delete(AIndex: Integer): IJclStringList; overload;
    function Delete(const AString: string): IJclStringList; overload;
    function Exchange(Index1, Index2: Integer): IJclStringList;
    function Add(const A: array of const): IJclStringList; overload;
    function AddStrings(const A: array of string): IJclStringList; overload;
    function BeginUpdate: IJclStringList;
    function EndUpdate: IJclStringList;
    function Trim: IJclStringList;
    function Join(const ASeparator: string = ''): string;
    function Split(const AText, ASeparator: string; AClearBeforeAdd: Boolean = True): IJclStringList;
    function ExtractWords(const AText: string; const ADelims: TSetOfChar = [#0..' ']; AClearBeforeAdd: Boolean = True): IJclStringList;
    function Last: string;
    function First: string;
    function LastIndex: Integer;
    function Clear: IJclStringList;
    function DeleteRegEx(const APattern: string): IJclStringList;
    function KeepRegEx(const APattern: string): IJclStringList;
    function Files(const APattern: string = '*'; ARecursive: Boolean = False; const ARegExPattern: string = ''): IJclStringList;
    function Directories(const APattern: string = '*'; ARecursive: Boolean = False; const ARegExPattern: string = ''): IJclStringList;
    function GetStringsRef: TStrings;
    function ConfigAsSet: IJclStringList;
    function Delimit(const ADelimiter: string): IJclStringList;
    function GetInterfaceByIndex(Index: Integer): IInterface;
    function GetLists(Index: Integer): IJclStringList;
    function GetVariants(AIndex: Integer): Variant;
    function GetKeyInterface(const AKey: string): IInterface;
    function GetKeyObject(const AKey: string): TObject;
    function GetKeyVariant(const AKey: string): Variant;
    function GetKeyList(const AKey: string): IJclStringList;
    function GetObjectsMode: TJclStringListObjectsMode;
    procedure SetInterfaceByIndex(Index: Integer; const Value: IInterface);
    procedure SetLists(Index: Integer; const Value: IJclStringList);
    procedure SetVariants(Index: Integer; const Value: Variant);
    procedure SetKeyInterface(const AKey: string; const Value: IInterface);
    procedure SetKeyObject(const AKey: string; const Value: TObject);
    procedure SetKeyVariant(const AKey: string; const Value: Variant);
    procedure SetKeyList(const AKey: string; const Value: IJclStringList);
    property Interfaces[Index: Integer]: IInterface read GetInterfaceByIndex write SetInterfaceByIndex;
    property Lists[Index: Integer]: IJclStringList read GetLists write SetLists;
    property Variants[Index: Integer]: Variant read GetVariants write SetVariants;
    property KeyList[const AKey: string]: IJclStringList read GetKeyList write SetKeyList;
    property KeyObject[const AKey: string]: TObject read GetKeyObject write SetKeyObject;
    property KeyInterface[const AKey: string]: IInterface read GetKeyInterface write SetKeyInterface;
    property KeyVariant[const AKey: string]: Variant read GetKeyVariant write SetKeyVariant;
    property ObjectsMode: TJclStringListObjectsMode read GetObjectsMode;
  end;

function JclStringList: IJclStringList; overload;
function JclStringListStrings(AStrings: TStrings): IJclStringList; overload;
function JclStringListStrings(const A: array of string): IJclStringList; overload;
function JclStringList(const A: array of const): IJclStringList; overload;
function JclStringList(const AText: string): IJclStringList; overload;

{$IFDEF UNITVERSIONING}
const
  UnitVersioning: TUnitVersionInfo = (
    RCSfile: '$URL: https://svn.sourceforge.net:443/svnroot/jcl/trunk/jcl/source/common/JclStringLists.pas $';
    Revision: '$Revision: 1856 $';
    Date: '$Date: 2006-12-30 10:04:59 +0100 (Sa, 30 Dez 2006) $';
    LogPath: 'JCL\source\common'
    );
{$ENDIF UNITVERSIONING}

implementation

uses
  TypInfo,
  JclFileUtils, JclPCRE, JclStrings;

type
  TUpdateControl = class(TObject, IInterface)
  private
    FStrings: TStrings;
  protected
    function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    constructor Create(AStrings: TStrings);
  end;

  TVariantWrapper = class(TObject)
  private
    FValue: Variant;
  end;

  TInterfaceWrapper = class(TObject)
  private
    FValue: IInterface;
  end;

  TJclStringListImpl = class(TStringList, IJclStringList)
  private
    FObjectsMode: TJclStringListObjectsMode;
    FSelfAsInterface: IJclStringList;
    FLastRegExPattern: string;
    FRegEx: TJclAnsiRegEx;
    FUpdateControl: TUpdateControl;
    function AutoUpdateControl: IInterface;
    function CanFreeObjects: Boolean;
    function MatchRegEx(const S, APattern: string): Boolean;
    function GetLists(Index: Integer): IJclStringList;
    function GetKeyInterface(const AKey: string): IInterface;
    function GetKeyObject(const AKey: string): TObject;
    function GetKeyVariant(const AKey: string): Variant;
    function GetValue(const Name: string): string;
    function GetVariants(AIndex: Integer): Variant;
    function GetKeyList(const AKey: string): IJclStringList;
    {$IFDEF COMPILER6_UP}
    function GetCaseSensitive: Boolean;
    {$ENDIF COMPILER6_UP}
    function GetDuplicates: TDuplicates;
    function GetOnChange: TNotifyEvent;
    function GetOnChanging: TNotifyEvent;
    function GetSorted: Boolean;
    function GetCommaText: string;
    {$IFDEF COMPILER6_UP}
    function GetDelimitedText: string;
    function GetDelimiter: Char;
    {$ENDIF COMPILER6_UP}
    function GetName(Index: Integer): string;
    {$IFDEF COMPILER7_UP}
    function GetNameValueSeparator: Char;
    function GetValueFromIndex(Index: Integer): string;
    {$ENDIF COMPILER7_UP}
    {$IFDEF COMPILER6_UP}
    function GetQuoteChar: Char;
    {$ENDIF COMPILER6_UP}
    function GetInterfaceByIndex(AIndex: Integer): IInterface;
    function GetObjects(Index: Integer): TObject;
    procedure SetValue(const Name, Value: string);
    procedure SetKeyList(const AKey: string; const Value: IJclStringList);
    procedure SetKeyInterface(const AKey: string; const Value: IInterface);
    procedure SetKeyObject(const AKey: string; const Value: TObject);
    procedure SetKeyVariant(const AKey: string; const Value: Variant);
    procedure SetLists(Index: Integer; const Value: IJclStringList);
    procedure SetVariants(Index: Integer; const Value: Variant);
    {$IFDEF COMPILER6_UP}
    procedure SetCaseSensitive(const Value: Boolean);
    {$ENDIF COMPILER6_UP}
    procedure SetDuplicates(const Value: TDuplicates);
    procedure SetOnChange(const Value: TNotifyEvent);
    procedure SetOnChanging(const Value: TNotifyEvent);
    procedure SetSorted(const Value: Boolean);
    procedure SetCommaText(const Value: string);
    {$IFDEF COMPILER6_UP}
    procedure SetDelimitedText(const Value: string);
    procedure SetDelimiter(const Value: Char);
    {$ENDIF COMPILER6_UP}
    {$IFDEF COMPILER7_UP}
    procedure SetNameValueSeparator(const Value: Char);
    procedure SetValueFromIndex(Index: Integer; const Value: string);
    {$ENDIF COMPILER7_UP}
    {$IFDEF COMPILER6_UP}
    procedure SetQuoteChar(const Value: Char);
    {$ENDIF COMPILER6_UP}
    procedure SetInterfaceByIndex(Index: Integer; const Value: IInterface);
    procedure SetObjects(Index: Integer; const Value: TObject);
    procedure EnsureObjectsMode(AMode: TJclStringListObjectsMode);
    function GetObjectsMode: TJclStringListObjectsMode;
  protected
    FRefCount: Integer;
    function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    {$IFDEF COMPILER5}
    function CompareStrings(const S1, S2: string): Integer; virtual;
    {$ENDIF COMPILER5}
  public
    constructor Create;
    destructor Destroy; override;
    function LoadExeParams: IJclStringList;
    function Exists(const S: string): Boolean;
    function ExistsName(const S: string): Boolean;
    function DeleteBlanks: IJclStringList;
    function KeepIntegers: IJclStringList;
    function DeleteIntegers: IJclStringList;
    function ReleaseInterfaces: IJclStringList;
    function FreeObjects(AFreeAndNil: Boolean = False): IJclStringList;
    function Clone: IJclStringList;
    function Add(const A: array of const): IJclStringList; reintroduce; overload;
    function AddStrings(const A: array of string): IJclStringList; reintroduce; overload;
    function BeginUpdate: IJclStringList;
    function EndUpdate: IJclStringList;
    function Trim: IJclStringList;
    function Delimit(const ADelimiter: string): IJclStringList;
    function Join(const ASeparator: string = ''): string;
    function Split(const AText, ASeparator: string; AClearBeforeAdd: Boolean = True): IJclStringList;
    function ExtractWords(const AText: string; const ADelims: TSetOfChar = [#0..' '];
      AClearBeforeAdd: Boolean = True): IJclStringList;
    function Last: string;
    function First: string;
    function LastIndex: Integer;
    function Clear: IJclStringList; reintroduce;
    function DeleteRegEx(const APattern: string): IJclStringList;
    function KeepRegEx(const APattern: string): IJclStringList;
    function Files(const APattern: string = '*'; ARecursive: Boolean = False;
      const ARegExPattern: string = ''): IJclStringList;
    function Directories(const APattern: string = '*'; ARecursive: Boolean = False;
      const ARegExPattern: string = ''): IJclStringList;
    function GetStringsRef: TStrings;
    function ConfigAsSet: IJclStringList;
    function Delete(AIndex: Integer): IJclStringList; reintroduce; overload;
    function Delete(const AString: string): IJclStringList; reintroduce; overload;
    function Exchange(Index1, Index2: Integer): IJclStringList; reintroduce;
    function Sort(ACompareFunction: TJclStringListSortCompare = nil): IJclStringList; reintroduce;
    function SortAsInteger: IJclStringList;
    function SortByName: IJclStringList;
    function Insert(Index: Integer; const S: string): IJclStringList; reintroduce;
    function InsertObject(Index: Integer; const S: string; AObject: TObject): IJclStringList; reintroduce;
    function LoadFromFile(const FileName: string): IJclStringList; reintroduce;
    function LoadFromStream(Stream: TStream): IJclStringList; reintroduce;
    function SaveToFile(const FileName: string): IJclStringList; reintroduce;
    function SaveToStream(Stream: TStream): IJclStringList; reintroduce;
    function Assign(Source: TPersistent): IJclStringList; reintroduce;
    { From TStrings/TStringList }
    property Values[const Name: string]: string read GetValue write SetValue;
    {$IFDEF COMPILER6_UP}
    property DelimitedText: string read GetDelimitedText write SetDelimitedText;
    property Delimiter: Char read GetDelimiter write SetDelimiter;
    {$ENDIF COMPILER6_UP}
    property Names[Index: Integer]: string read GetName;
    {$IFDEF COMPILER6_UP}
    property QuoteChar: Char read GetQuoteChar write SetQuoteChar;
    {$ENDIF COMPILER6_UP}
    property CommaText: string read GetCommaText write SetCommaText;
    {$IFDEF COMPILER7_UP}
    property ValueFromIndex[Index: Integer]: string read GetValueFromIndex write SetValueFromIndex;
    property NameValueSeparator: Char read GetNameValueSeparator write SetNameValueSeparator;
    {$ENDIF COMPILER7_UP}
    property Duplicates: TDuplicates read GetDuplicates write SetDuplicates;
    property Sorted: Boolean read GetSorted write SetSorted;
    {$IFDEF COMPILER6_UP}
    property CaseSensitive: Boolean read GetCaseSensitive write SetCaseSensitive;
    {$ENDIF COMPILER6_UP}
    property OnChange: TNotifyEvent read GetOnChange write SetOnChange;
    property OnChanging: TNotifyEvent read GetOnChanging write SetOnChanging;
    { New }
    property Objects[Index: Integer]: TObject read GetObjects write SetObjects;
    property Interfaces[Index: Integer]: IInterface read GetInterfaceByIndex write SetInterfaceByIndex;
    property Lists[Index: Integer]: IJclStringList read GetLists write SetLists;
    property Variants[Index: Integer]: Variant read GetVariants write SetVariants;
    property KeyList[const AKey: string]: IJclStringList read GetKeyList write SetKeyList;
    property KeyObject[const AKey: string]: TObject read GetKeyObject write SetKeyObject;
    property KeyInterface[const AKey: string]: IInterface read GetKeyInterface write SetKeyInterface;
    property KeyVariant[const AKey: string]: Variant read GetKeyVariant write SetKeyVariant;
    property ObjectsMode: TJclStringListObjectsMode read GetObjectsMode;
  end;

function JclStringList: IJclStringList;
begin
  Result := TJclStringListImpl.Create;
end;

function JclStringList(const AText: string): IJclStringList; overload;
begin
  Result := JclStringList;
  Result.Text := AText;
end;

function JclStringListStrings(AStrings: TStrings): IJclStringList; overload;
begin
  Result := JclStringList;
  Result.AddStrings(AStrings);
end;

function JclStringListStrings(const A: array of string): IJclStringList;
begin
  Result := JclStringList.AddStrings(A);
end;

function JclStringList(const A: array of const): IJclStringList;
begin
  Result := JclStringList.Add(A);
end;

//=== { TJclStringListImpl } =================================================

function TJclStringListImpl.Add(const A: array of const): IJclStringList;
const
  BoolToStr: array [Boolean] of string[5] = ('false', 'true');
var
  I: Integer;
begin
  AutoUpdateControl;
  for I := Low(A) to High(A) do
    with A[I] do
      case VType of
        vtInteger:
          Add(IntToStr(VInteger));
        vtBoolean:
          Add(BoolToStr[VBoolean]);
        vtChar:
          Add(VChar);
        vtExtended:
          Add(FloatToStr(VExtended^));
        vtString:
          Add(VString^);
        vtPChar:
          Add(VPChar);
        vtObject:
          Add(VObject.ClassName);
        vtClass:
          Add(VClass.ClassName);
        vtAnsiString:
          Add(string(VAnsiString));
        vtCurrency:
          Add(CurrToStr(VCurrency^));
        vtVariant:
          Add(string(VVariant^));
        vtInt64:
          Add(IntToStr(VInt64^));
      end;
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.AddStrings(const A: array of string): IJclStringList;
var
  I: Integer;
begin
  AutoUpdateControl;
  for I := Low(A) to High(A) do
    Add(A[I]);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.BeginUpdate: IJclStringList;
begin
  inherited BeginUpdate;
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.AutoUpdateControl: IInterface;
begin
  Result := FUpdateControl as IInterface;
end;

function TJclStringListImpl.Clear: IJclStringList;
begin
  if CanFreeObjects then
    FreeObjects(False);
  inherited Clear;
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.EndUpdate: IJclStringList;
begin
  inherited EndUpdate;
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.ExtractWords(const AText: string; const ADelims: TSetOfChar;
  AClearBeforeAdd: Boolean): IJclStringList;
var
  L, I, X: Integer;
begin
  AutoUpdateControl;
  if AClearBeforeAdd then
    Clear;
  I := 1;
  L := Length(AText);
  while I <= L do
  begin
    while (I <= L) and (AText[I] in ADelims) do
      Inc(I);
    X := I;
    while (I <= L) and not (AText[I] in ADelims) do
      Inc(I);
    if X <> I then
      Add(Copy(AText, X, I - X));
  end;
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.First: string;
begin
  Result := Strings[0];
end;

function TJclStringListImpl.Join(const ASeparator: string): string;
var
  I: Integer;
begin
  Result := '';
  for I := 0 to LastIndex - 1 do
    Result := Result + Strings[I] + ASeparator;
  if Count > 0 then
    Result := Result + Last;
end;

function TJclStringListImpl.Last: string;
begin
  Result := Strings[LastIndex];
end;

function TJclStringListImpl.QueryInterface(const IID: TGUID; out Obj): HRESULT;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

function TJclStringListImpl.Split(const AText, ASeparator: string;
  AClearBeforeAdd: Boolean = True): IJclStringList;
var
  LStartIndex, LEndIndex: Integer;
  LLengthSeparator: Integer;
begin
  if AText <> '' then
  begin
    AutoUpdateControl;
    if AClearBeforeAdd then
      Clear;
    LLengthSeparator := Length(ASeparator);
    LStartIndex := 1;
    LEndIndex := StrSearch(ASeparator, AText, LStartIndex);
    while LEndIndex > 0 do
    begin
      Add(Copy(AText, LStartIndex, LEndIndex - LStartIndex));
      LStartIndex := LEndIndex + LLengthSeparator;
      LEndIndex := StrSearch(ASeparator, AText, LStartIndex);
    end;
    Add(Copy(AText, LStartIndex, MaxInt));
  end;
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.Trim: IJclStringList;
var
  I: Integer;
begin
  AutoUpdateControl;
  for I := 0 to LastIndex do
    Strings[I] := SysUtils.Trim(Strings[I]);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl._AddRef: Integer;
begin
  Result := InterlockedIncrement(FRefCount);
end;

function TJclStringListImpl._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount);
  if Result = 1 then
  begin
    // When there is only one reference, it is the internal reference,
    // so we release it. The compiler will call _Release again and
    // the object will be destroyed.
    FSelfAsInterface := nil;
  end
  else
  if Result = 0 then
    Destroy;
end;

function TJclStringListImpl.DeleteRegEx(const APattern: string): IJclStringList;
var
  I: Integer;
begin
  AutoUpdateControl;
  for I := LastIndex downto 0 do
    if MatchRegEx(Strings[I], APattern) then
      Delete(I);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.KeepRegEx(const APattern: string): IJclStringList;
var
  I: Integer;
begin
  AutoUpdateControl;
  for I := LastIndex downto 0 do
    if not MatchRegEx(Strings[I], APattern) then
      Delete(I);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.MatchRegEx(const S, APattern: string): Boolean;
begin
  if FRegEx = nil then
    FRegEx := TJclAnsiRegEx.Create;
  if FLastRegExPattern <> APattern then
  begin
    {$IFDEF COMPILER6_UP}
    if CaseSensitive then
      FRegEx.Options := FRegEx.Options - [roIgnoreCase]
    else
      FRegEx.Options := FRegEx.Options + [roIgnoreCase];
    {$ENDIF COMPILER6_UP}
    FRegEx.Compile(APattern, False, True);
    FLastRegExPattern := APattern;
  end;
  Result := FRegEx.Match(S);
end;

destructor TJclStringListImpl.Destroy;
begin
  if CanFreeObjects then
    FreeObjects(False);
  FreeAndNil(FUpdateControl);
  FreeAndNil(FRegEx);
  inherited Destroy;
end;

function TJclStringListImpl.Directories(const APattern: string = '*';
  ARecursive: Boolean = False; const ARegExPattern: string = ''): IJclStringList;

  procedure DoDirectories(const APattern: string);
  var
    LSearchRec: TSearchRec;
    LFullName: string;
    LPath: string;
  begin
    LPath := ExtractFilePath(APattern);
    if FindFirst(APattern, faAnyFile, LSearchRec) = 0 then
      try
        repeat
          if (LSearchRec.Attr and faDirectory = 0) or
             (LSearchRec.Name = '.') or (LSearchRec.Name = '..') then
            Continue;
          LFullName := LPath + LSearchRec.Name;
          if (ARegExPattern = '') or MatchRegEx(LFullName, ARegExPattern) then
            Add(LFullName);
          if ARecursive then
            DoDirectories(PathAddSeparator(LFullName) + ExtractFileName(APattern));
        until FindNext(LSearchRec) <> 0;
      finally
        FindClose(LSearchRec);
      end;
  end;

begin
  AutoUpdateControl;
  if DirectoryExists(APattern) then
    DoDirectories(PathAddSeparator(APattern) + '*')
  else
    DoDirectories(APattern);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.Files(const APattern: string = '*';
  ARecursive: Boolean = False; const ARegExPattern: string = ''): IJclStringList;

  procedure DoFiles(const APattern: string);
  var
    LSearchRec: TSearchRec;
    LFullName: string;
    LDirectories: IJclStringList;
    LPath: string;
    I: Integer;
  begin
    LPath := ExtractFilePath(APattern);
    if FindFirst(APattern, faAnyFile and not faDirectory, LSearchRec) = 0 then
    begin
      try
        repeat
          if (LSearchRec.Attr and faDirectory <> 0) or
             (LSearchRec.Name = '.') or (LSearchRec.Name = '..') then
            Continue;
          LFullName := LPath + LSearchRec.Name;
          if (ARegExPattern = '') or MatchRegEx(LFullName, ARegExPattern) then
            Add(LFullName);
        until FindNext(LSearchRec) <> 0;
      finally
        FindClose(LSearchRec);
      end;
    end;
    if ARecursive then
    begin
      LDirectories := JclStringList.Directories(LPath + '*', False);
      for I := 0 to LDirectories.LastIndex do
        DoFiles(PathAddSeparator(LDirectories[I]) + ExtractFileName(APattern));
    end;
  end;

begin
  AutoUpdateControl;
  if DirectoryExists(APattern) then
    DoFiles(PathAddSeparator(APattern) + '*')
  else
    DoFiles(APattern);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.LastIndex: Integer;
begin
  { The code bellow is more optimized than "Result := Count - 1". }
  Result := Count;
  Dec(Result);
end;

constructor TJclStringListImpl.Create;
begin
  inherited Create;
  FUpdateControl := TUpdateControl.Create(Self);
  if QueryInterface(IJclStringList, FSelfAsInterface) <> 0 then
    {$IFDEF COMPILER5}
    RunError(228 { reIntfCastError });
    {$ELSE}
    System.Error(reIntfCastError);
    {$ENDIF COMPILER5}
end;

function TJclStringListImpl.GetLists(Index: Integer): IJclStringList;
begin
  Result := Interfaces[Index] as IJclStringList;
  if Result = nil then
  begin
    Result := JclStringList;
    Interfaces[Index] := Result;
  end;
end;

procedure TJclStringListImpl.SetLists(Index: Integer; const Value: IJclStringList);
begin
  Interfaces[Index] := Value;
end;

function TJclStringListImpl.GetStringsRef: TStrings;
begin
  Result := Self;
end;

function TJclStringListImpl.GetKeyInterface(const AKey: string): IInterface;
var
  I: Integer;
begin
  I := IndexOf(AKey);
  if I >= 0 then
    Result := Interfaces[I]
  else
    Result := nil;
end;

function TJclStringListImpl.GetKeyObject(const AKey: string): TObject;
var
  I: Integer;
begin
  I := IndexOf(AKey);
  if I >= 0 then
    Result := Objects[I]
  else
    Result := nil;
end;

procedure TJclStringListImpl.SetKeyInterface(const AKey: string; const Value: IInterface);
var
  I: Integer;
begin
  I := IndexOf(AKey);
  if I < 0 then
    I := Add(AKey);
  Interfaces[I] := Value
end;

procedure TJclStringListImpl.SetKeyObject(const AKey: string; const Value: TObject);
var
  I: Integer;
begin
  I := IndexOf(AKey);
  if I < 0 then
    AddObject(AKey, Value)
  else
    Objects[I] := Value;
end;

function TJclStringListImpl.ConfigAsSet: IJclStringList;
begin
  Sorted := True;
  Duplicates := dupIgnore;
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.GetKeyVariant(const AKey: string): Variant;
var
  I: Integer;
begin
  I := IndexOf(AKey);
  if I >= 0 then
    Result := Variants[I]
  else
    Result := Unassigned;
end;

procedure TJclStringListImpl.SetKeyVariant(const AKey: string; const Value: Variant);
var
  I: Integer;
begin
  I := IndexOf(AKey);
  if I < 0 then
    I := Add(AKey);
  Variants[I] := Value
end;

function TJclStringListImpl.GetValue(const Name: string): string;
begin
  Result := inherited Values[Name];
end;

procedure TJclStringListImpl.SetValue(const Name, Value: string);
begin
  inherited Values[Name] := Value;
end;

function TJclStringListImpl.GetInterfaceByIndex(AIndex: Integer): IInterface;
var
  V: TInterfaceWrapper;
begin
  if FObjectsMode <> omInterfaces then
    EnsureObjectsMode(omInterfaces);
  V := TInterfaceWrapper(inherited Objects[AIndex]);
  if V = nil then
    Result := nil
  else
    Result := V.FValue;
end;

procedure TJclStringListImpl.SetInterfaceByIndex(Index: Integer; const Value: IInterface);
var
  V: TInterfaceWrapper;
begin
  if FObjectsMode <> omInterfaces then
    EnsureObjectsMode(omInterfaces);
  V := TInterfaceWrapper(inherited Objects[Index]);
  if V = nil then
  begin
    V := TInterfaceWrapper.Create;
    inherited Objects[Index] := V;
  end;
  V.FValue := Value;
end;

function TJclStringListImpl.GetObjects(Index: Integer): TObject;
begin
  if FObjectsMode <> omObjects then
    EnsureObjectsMode(omObjects);
  Result := inherited Objects[Index];
end;

procedure TJclStringListImpl.SetObjects(Index: Integer; const Value: TObject);
begin
  if FObjectsMode <> omObjects then
    EnsureObjectsMode(omObjects);
  inherited Objects[Index] := Value;
end;

function TJclStringListImpl.GetVariants(AIndex: Integer): Variant;
var
  V: TVariantWrapper;
begin
  if FObjectsMode <> omVariants then
    EnsureObjectsMode(omVariants);
  V := TVariantWrapper(inherited Objects[AIndex]);
  if V = nil then
    Result := Unassigned
  else
    Result := V.FValue;
end;

procedure TJclStringListImpl.SetVariants(Index: Integer; const Value: Variant);
var
  V: TVariantWrapper;
begin
  if FObjectsMode <> omVariants then
    EnsureObjectsMode(omVariants);
  V := TVariantWrapper(inherited Objects[Index]);
  if V = nil then
  begin
    V := TVariantWrapper.Create;
    inherited Objects[Index] := V;
  end;
  V.FValue := Value;
end;

procedure TJclStringListImpl.EnsureObjectsMode(AMode: TJclStringListObjectsMode);
begin
  if FObjectsMode <> AMode then
  begin
    if FObjectsMode <> omNone then
    begin
      raise Exception.CreateFmt('Objects cannot be used as "%s" because it has been used as "%s".',
        [GetEnumName(TypeInfo(TJclStringListObjectsMode), Ord(AMode)),
        GetEnumName(TypeInfo(TJclStringListObjectsMode), Ord(FObjectsMode))]);
    end;
    FObjectsMode := AMode;
  end;
end;

function TJclStringListImpl.GetKeyList(const AKey: string): IJclStringList;
begin
  Result := KeyInterface[AKey] as IJclStringList;
  if Result = nil then
  begin
    Result := JclStringList;
    KeyInterface[AKey] := Result;
  end;
end;

procedure TJclStringListImpl.SetKeyList(const AKey: string; const Value: IJclStringList);
begin
  KeyInterface[AKey] := Value;
end;

function TJclStringListImpl.Delete(AIndex: Integer): IJclStringList;
begin
  if CanFreeObjects then
    inherited Objects[AIndex].Free;
  inherited Delete(AIndex);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.Delete(const AString: string): IJclStringList;
begin
  Result := Delete(IndexOf(AString));
end;

function TJclStringListImpl.Exchange(Index1, Index2: Integer): IJclStringList;
begin
  inherited Exchange(Index1, Index2);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.Sort(ACompareFunction: TJclStringListSortCompare = nil): IJclStringList;

  function LocalSort(List: TStringList; Index1, Index2: Integer): Integer;
  begin
    Result := ACompareFunction(FSelfAsInterface, Index1, Index2);
  end;

begin
  if not Assigned(ACompareFunction) then
    inherited Sort
  else
    inherited CustomSort(@LocalSort);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.SortAsInteger: IJclStringList;

  function LocalSortAsInteger(List: TStringList; Index1, Index2: Integer): Integer;
  begin
    Result := StrToInt(List[Index1]) - StrToInt(List[Index2]);
  end;

begin
  inherited CustomSort(@LocalSortAsInteger);
  Result := FSelfAsInterface;
end;

{$IFDEF COMPILER5}
function TJclStringListImpl.CompareStrings(const S1, S2: string): Integer;
begin
  Result := AnsiCompareText(S1, S2);
end;
{$ENDIF COMPILER5}

function TJclStringListImpl.SortByName: IJclStringList;

  function LocalSortByName(List: TStringList; Index1, Index2: Integer): Integer;
  begin
    Result := TJclStringListImpl(List).CompareStrings(List.Names[Index1], List.Names[Index2]);
  end;

begin
  inherited CustomSort(@LocalSortByName);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.Insert(Index: Integer; const S: string): IJclStringList;
begin
  inherited Insert(Index, S);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.InsertObject(Index: Integer; const S: string; AObject: TObject): IJclStringList;
begin
  inherited InsertObject(Index, S, AObject);
  Result := FSelfAsInterface;
end;

{$IFDEF COMPILER6_UP}
function TJclStringListImpl.GetCaseSensitive: Boolean;
begin
  Result := inherited CaseSensitive;
end;
{$ENDIF COMPILER6_UP}

function TJclStringListImpl.GetDuplicates: TDuplicates;
begin
  Result := inherited Duplicates;
end;

function TJclStringListImpl.GetOnChange: TNotifyEvent;
begin
  Result := inherited OnChange;
end;

function TJclStringListImpl.GetOnChanging: TNotifyEvent;
begin
  Result := inherited OnChanging;
end;

function TJclStringListImpl.GetSorted: Boolean;
begin
  Result := inherited Sorted;
end;

{$IFDEF COMPILER6_UP}
procedure TJclStringListImpl.SetCaseSensitive(const Value: Boolean);
begin
  inherited CaseSensitive := Value;
end;
{$ENDIF COMPILER6_UP}

procedure TJclStringListImpl.SetDuplicates(const Value: TDuplicates);
begin
  inherited Duplicates := Value;
end;

procedure TJclStringListImpl.SetOnChange(const Value: TNotifyEvent);
begin
  inherited OnChange := Value;
end;

procedure TJclStringListImpl.SetOnChanging(const Value: TNotifyEvent);
begin
  inherited OnChanging := Value;
end;

procedure TJclStringListImpl.SetSorted(const Value: Boolean);
begin
  inherited Sorted := Value;
end;

function TJclStringListImpl.LoadFromFile(const FileName: string): IJclStringList;
begin
  inherited LoadFromFile(FileName);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.LoadFromStream(Stream: TStream): IJclStringList;
begin
  inherited LoadFromStream(Stream);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.SaveToFile(const FileName: string): IJclStringList;
begin
  inherited SaveToFile(FileName);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.SaveToStream(Stream: TStream): IJclStringList;
begin
  inherited SaveToStream(Stream);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.GetCommaText: string;
begin
  Result := inherited CommaText;
end;

{$IFDEF COMPILER6_UP}

function TJclStringListImpl.GetDelimitedText: string;
begin
  Result := inherited DelimitedText;
end;

function TJclStringListImpl.GetDelimiter: Char;
begin
  Result := inherited Delimiter;
end;

{$ENDIF COMPILER6_UP}

function TJclStringListImpl.GetName(Index: Integer): string;
begin
  Result := inherited Names[Index];
end;

{$IFDEF COMPILER7_UP}

function TJclStringListImpl.GetNameValueSeparator: Char;
begin
  Result := inherited NameValueSeparator;
end;

function TJclStringListImpl.GetValueFromIndex(Index: Integer): string;
begin
  Result := inherited ValueFromIndex[Index];
end;

{$ENDIF COMPILER7_UP}

{$IFDEF COMPILER6_UP}
function TJclStringListImpl.GetQuoteChar: Char;
begin
  Result := inherited QuoteChar;
end;
{$ENDIF COMPILER6_UP}

procedure TJclStringListImpl.SetCommaText(const Value: string);
begin
  inherited CommaText := Value;
end;

{$IFDEF COMPILER6_UP}

procedure TJclStringListImpl.SetDelimitedText(const Value: string);
begin
  inherited DelimitedText := Value;
end;

procedure TJclStringListImpl.SetDelimiter(const Value: Char);
begin
  inherited Delimiter := Value;
end;

{$ENDIF COMPILER6_UP}

{$IFDEF COMPILER7_UP}

procedure TJclStringListImpl.SetNameValueSeparator(const Value: Char);
begin
  inherited NameValueSeparator := Value;
end;

procedure TJclStringListImpl.SetValueFromIndex(Index: Integer; const Value: string);
begin
  inherited ValueFromIndex[Index] := Value;
end;

{$ENDIF COMPILER7_UP}

{$IFDEF COMPILER6_UP}
procedure TJclStringListImpl.SetQuoteChar(const Value: Char);
begin
  inherited QuoteChar := Value;
end;
{$ENDIF COMPILER6_UP}

function TJclStringListImpl.Delimit(const ADelimiter: string): IJclStringList;
var
  I: Integer;
begin
  AutoUpdateControl;
  for I := 0 to LastIndex do
    Strings[I] := ADelimiter + Strings[I] + ADelimiter;
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.LoadExeParams: IJclStringList;
var
  I: Integer;
  S: string;
begin
  AutoUpdateControl;
  Clear;
  for I := 1 to ParamCount do
  begin
    S := ParamStr(I);
    if S[1] in ['-', '/'] then
      System.Delete(S, 1, 1);
    Add(S);
  end;
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.Exists(const S: string): Boolean;
begin
  Result := IndexOf(S) >= 0;
end;

function TJclStringListImpl.ExistsName(const S: string): Boolean;
begin
  Result := IndexOfName(S) >= 0;
end;

function TJclStringListImpl.DeleteBlanks: IJclStringList;
var
  I: Integer;
begin
  AutoUpdateControl;
  for I := LastIndex downto 0 do
    if SysUtils.Trim(Strings[I]) = '' then
      Delete(I);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.KeepIntegers: IJclStringList;
var
  I, X: Integer;
begin
  AutoUpdateControl;
  for I := LastIndex downto 0 do
    if not TryStrToInt(Strings[I], X) then
      Delete(I);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.DeleteIntegers: IJclStringList;
var
  I, X: Integer;
begin
  AutoUpdateControl;
  for I := LastIndex downto 0 do
    if TryStrToInt(Strings[I], X) then
      Delete(I);
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.FreeObjects(AFreeAndNil: Boolean = False): IJclStringList;
var
  I: Integer;
begin
  if AFreeAndNil then
    AutoUpdateControl;
  for I := 0 to LastIndex do
  begin
    inherited Objects[I].Free;
    if AFreeAndNil then
      inherited Objects[I] := nil;
  end;
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.ReleaseInterfaces: IJclStringList;
var
  I: Integer;
begin
  AutoUpdateControl;
  for I := 0 to LastIndex do
    Interfaces[I] := nil;
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.Clone: IJclStringList;
begin
  Result := JclStringList.Assign(Self);
end;

function TJclStringListImpl.Assign(Source: TPersistent): IJclStringList;
var
  L: TJclStringListImpl;
  I: Integer;
begin
  inherited Assign(Source);
  if Source is TJclStringListImpl then
  begin
    L := TJclStringListImpl(Source);
    FObjectsMode := L.FObjectsMode;
    if not (FObjectsMode in [omNone, omObjects]) then
    begin
      AutoUpdateControl;
      for I := 0 to LastIndex do
      begin
        inherited Objects[I] := nil;
        case FObjectsMode of
          omVariants:
            Variants[I] := L.Variants[I];
          omInterfaces:
            Interfaces[I] := L.Interfaces[I];
        end;
      end;
    end;
  end;
  Result := FSelfAsInterface;
end;

function TJclStringListImpl.CanFreeObjects: Boolean;
begin
  Result := not (FObjectsMode in [omNone, omObjects]);
end;

function TJclStringListImpl.GetObjectsMode: TJclStringListObjectsMode;
begin
  Result := FObjectsMode;
end;

//=== { TUpdateControl } =====================================================

constructor TUpdateControl.Create(AStrings: TStrings);
begin
  inherited Create;
  FStrings := AStrings;
end;

function TUpdateControl._AddRef: Integer;
begin
  FStrings.BeginUpdate;
  Result := 0;
end;

function TUpdateControl._Release: Integer;
begin
  FStrings.EndUpdate;
  Result := 0;
end;

function TUpdateControl.QueryInterface(const IID: TGUID; out Obj): HRESULT;
begin
  if GetInterface(IID, Obj) then
    Result := S_OK
  else
    Result := E_NOINTERFACE;
end;

{$IFDEF UNITVERSIONING}
initialization
  RegisterUnitVersion(HInstance, UnitVersioning);

finalization
  UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}

end.
