mirror of
https://github.com/danieleteti/delphimvcframework.git
synced 2024-11-15 15:55:54 +01:00
11524 lines
385 KiB
ObjectPascal
11524 lines
385 KiB
ObjectPascal
/// Framework Core Low-Level Data Processing Functions
|
|
// - this unit is a part of the Open Source Synopse mORMot framework 2,
|
|
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
|
|
unit mormot.core.data;
|
|
|
|
{
|
|
*****************************************************************************
|
|
|
|
Low-Level Data Processing Functions shared by all framework units
|
|
- RTL TPersistent / TInterfacedObject with Custom Constructor
|
|
- TSynPersistent* TSyn*List TSynLocker classes
|
|
- TSynPersistentStore with proper Binary Serialization
|
|
- INI Files and In-memory Access
|
|
- Efficient RTTI Values Binary Serialization and Comparison
|
|
- TDynArray and TDynArrayHashed Wrappers
|
|
- Integer Arrays Extended Process
|
|
- RawUtf8 String Values Interning and TRawUtf8List
|
|
- Abstract Radix Tree Classes
|
|
|
|
*****************************************************************************
|
|
}
|
|
|
|
interface
|
|
|
|
{$I mormot.defines.inc}
|
|
|
|
uses
|
|
classes,
|
|
contnrs,
|
|
types,
|
|
sysutils,
|
|
{$ifdef ISDELPHI}
|
|
typinfo, // circumvent Delphi inlining issues
|
|
{$endif ISDELPHI}
|
|
mormot.core.base,
|
|
mormot.core.os,
|
|
mormot.core.rtti,
|
|
mormot.core.datetime,
|
|
mormot.core.unicode,
|
|
mormot.core.text,
|
|
mormot.core.buffers;
|
|
|
|
|
|
{ ************ RTL TPersistent / TInterfacedObject with Custom Constructor }
|
|
|
|
type
|
|
/// abstract parent class with a virtual constructor, ready to be overridden
|
|
// to initialize the instance
|
|
// - you can specify such a class if you need an object including published
|
|
// properties (like TPersistent) with a virtual constructor (e.g. to
|
|
// initialize some nested class properties)
|
|
TPersistentWithCustomCreate = class(TPersistent)
|
|
public
|
|
/// this virtual constructor will be called at instance creation
|
|
// - this constructor does nothing, but is declared as virtual so that
|
|
// inherited classes may safely override this default void implementation
|
|
constructor Create; virtual;
|
|
end;
|
|
|
|
{$M+}
|
|
/// abstract parent class with threadsafe implementation of IInterface and
|
|
// a virtual constructor
|
|
// - you can specify e.g. such a class to TRestServer.ServiceRegister() if
|
|
// you need an interfaced object with a virtual constructor, ready to be
|
|
// overridden to initialize the instance
|
|
TInterfacedObjectWithCustomCreate = class(TInterfacedObject)
|
|
public
|
|
/// this virtual constructor will be called at instance creation
|
|
// - this constructor does nothing, but is declared as virtual so that
|
|
// inherited classes may safely override this default void implementation
|
|
constructor Create; virtual;
|
|
/// used to mimic TInterfacedObject reference counting
|
|
// - Release=true will call TInterfacedObject._Release
|
|
// - Release=false will call TInterfacedObject._AddRef
|
|
// - could be used to emulate proper reference counting of the instance
|
|
// via interfaces variables, but still storing plain class instances
|
|
// (e.g. in a global list of instances) - warning: use with extreme caution!
|
|
procedure RefCountUpdate(Release: boolean); virtual;
|
|
end;
|
|
{$M-}
|
|
|
|
|
|
/// an abstract ancestor, for implementing a custom TInterfacedObject like class
|
|
// - by default, will do nothing: no instance would be retrieved by
|
|
// QueryInterface unless the VirtualQueryInterface protected method is
|
|
// overriden, and _AddRef/_Release methods would call VirtualAddRef and
|
|
// VirtualRelease pure abstract methods
|
|
// - using this class will leverage the signature difference between Delphi
|
|
// and FPC, among all supported platforms
|
|
// - the class includes a RefCount integer field
|
|
TSynInterfacedObject = class(TObject, IUnknown)
|
|
protected
|
|
fRefCount: integer;
|
|
// returns E_NOINTERFACE by default
|
|
function VirtualQueryInterface(IID: PGuid; out Obj): TIntQry; virtual;
|
|
// always return 1 for a "non allocated" instance (0 triggers release)
|
|
function VirtualAddRef: integer; virtual; abstract;
|
|
function VirtualRelease: integer; virtual; abstract;
|
|
function QueryInterface({$ifdef FPC_HAS_CONSTREF}constref{$else}const{$endif}
|
|
IID: TGuid; out Obj): TIntQry; {$ifdef OSWINDOWS}stdcall{$else}cdecl{$endif};
|
|
function _AddRef: TIntCnt; {$ifdef OSWINDOWS}stdcall{$else}cdecl{$endif};
|
|
function _Release: TIntCnt; {$ifdef OSWINDOWS}stdcall{$else}cdecl{$endif};
|
|
public
|
|
/// this virtual constructor will be called at instance creation
|
|
// - this constructor does nothing, but is declared as virtual so that
|
|
// inherited classes may safely override this default void implementation
|
|
constructor Create; virtual;
|
|
/// the associated reference count
|
|
property RefCount: integer
|
|
read fRefCount write fRefCount;
|
|
end;
|
|
|
|
/// any TCollection used between client and server shall inherit from this class
|
|
// - you should override the GetClass virtual method to provide the
|
|
// expected collection item class to be used on server side
|
|
// - another possibility is to register a TCollection/TCollectionItem pair
|
|
// via a call to Rtti.RegisterCollection()
|
|
TInterfacedCollection = class(TCollection)
|
|
public
|
|
/// you shall override this abstract method
|
|
class function GetClass: TCollectionItemClass; virtual; abstract;
|
|
/// this constructor will call GetClass to initialize the collection
|
|
constructor Create; reintroduce; virtual;
|
|
end;
|
|
|
|
/// used to determine the exact class type of a TInterfacedObjectWithCustomCreate
|
|
// - could be used to create instances using its virtual constructor
|
|
TInterfacedObjectWithCustomCreateClass = class of TInterfacedObjectWithCustomCreate;
|
|
|
|
/// used to determine the exact class type of a TPersistentWithCustomCreateClass
|
|
// - could be used to create instances using its virtual constructor
|
|
TPersistentWithCustomCreateClass = class of TPersistentWithCustomCreate;
|
|
|
|
/// class-reference type (metaclass) of a TInterfacedCollection kind
|
|
TInterfacedCollectionClass = class of TInterfacedCollection;
|
|
|
|
|
|
/// interface for TAutoFree to register another TObject instance
|
|
// to an existing IAutoFree local variable
|
|
// - WARNING: both FPC and Delphi 10.4+ don't keep the IAutoFree instance
|
|
// up to the end-of-method -> you should not use TAutoFree for new projects :(
|
|
IAutoFree = interface
|
|
procedure Another(var objVar; obj: TObject);
|
|
/// do-nothing method to circumvent the Delphi 10.4 IAutoFree early release
|
|
procedure ForMethod;
|
|
end;
|
|
|
|
/// simple reference-counted storage for local objects
|
|
// - WARNING: both FPC and Delphi 10.4+ don't keep the IAutoFree instance
|
|
// up to the end-of-method -> you should not use TAutoFree for new projects :(
|
|
// - be aware that it won't implement a full ARC memory model, but may be
|
|
// just used to avoid writing some try ... finally blocks on local variables
|
|
// - use with caution, only on well defined local scope
|
|
TAutoFree = class(TInterfacedObject, IAutoFree)
|
|
protected
|
|
fObject: TObject;
|
|
fObjectList: array of TObject;
|
|
// do-nothing method to circumvent the Delphi 10.4 IAutoFree early release
|
|
procedure ForMethod;
|
|
public
|
|
/// initialize the TAutoFree class for one local variable
|
|
// - do not call this constructor, but class function One() instead
|
|
constructor Create(var localVariable; obj: TObject); reintroduce; overload;
|
|
/// initialize the TAutoFree class for several local variables
|
|
// - do not call this constructor, but class function Several() instead
|
|
constructor Create(const varObjPairs: array of pointer); reintroduce; overload;
|
|
/// protect one local TObject variable instance life time
|
|
// - for instance, instead of writing:
|
|
// !var
|
|
// ! myVar: TMyClass;
|
|
// !begin
|
|
// ! myVar := TMyClass.Create;
|
|
// ! try
|
|
// ! ... use myVar
|
|
// ! finally
|
|
// ! myVar.Free;
|
|
// ! end;
|
|
// !end;
|
|
// - you may write:
|
|
// !var
|
|
// ! myVar: TMyClass;
|
|
// !begin
|
|
// ! TAutoFree.One(myVar,TMyClass.Create);
|
|
// ! ... use myVar
|
|
// !end; // here myVar will be released
|
|
// - warning: under FPC, you should assign the result of this method to a local
|
|
// IAutoFree variable - see bug http://bugs.freepascal.org/view.php?id=26602
|
|
// - Delphi 10.4 also did change it and release the IAutoFree before the
|
|
// end of the current method, so we inlined a void method call trying to
|
|
// circumvent this problem - https://quality.embarcadero.com/browse/RSP-30050
|
|
// - for both Delphi 10.4+ and FPC, you may use with TAutoFree.One() do
|
|
class function One(var localVariable; obj: TObject): IAutoFree;
|
|
{$ifdef ISDELPHI104} inline; {$endif}
|
|
/// protect several local TObject variable instances life time
|
|
// - specified as localVariable/objectInstance pairs
|
|
// - you may write:
|
|
// !var
|
|
// ! var1, var2: TMyClass;
|
|
// !begin
|
|
// ! TAutoFree.Several([
|
|
// ! @var1,TMyClass.Create,
|
|
// ! @var2,TMyClass.Create]);
|
|
// ! ... use var1 and var2
|
|
// !end; // here var1 and var2 will be released
|
|
// - warning: under FPC, you should assign the result of this method to a local
|
|
// IAutoFree variable - see bug http://bugs.freepascal.org/view.php?id=26602
|
|
// - Delphi 10.4 also did change it and release the IAutoFree before the
|
|
// end of the current method, and an "array of pointer" cannot be inlined
|
|
// by the Delphi compiler, so you should explicitly call ForMethod:
|
|
// ! TAutoFree.Several([
|
|
// ! @var1,TMyClass.Create,
|
|
// ! @var2,TMyClass.Create]).ForMethod;
|
|
class function Several(const varObjPairs: array of pointer): IAutoFree;
|
|
/// protect another TObject variable to an existing IAutoFree instance life time
|
|
// - you may write:
|
|
// !var
|
|
// ! var1, var2: TMyClass;
|
|
// ! auto: IAutoFree;
|
|
// !begin
|
|
// ! auto := TAutoFree.One(var1,TMyClass.Create);,
|
|
// ! .... do something
|
|
// ! auto.Another(var2,TMyClass.Create);
|
|
// ! ... use var1 and var2
|
|
// !end; // here var1 and var2 will be released
|
|
procedure Another(var localVariable; obj: TObject);
|
|
/// will finalize the associated TObject instances
|
|
// - note that releasing the TObject instances won't be protected, so
|
|
// any exception here may induce a memory leak: use only with "safe"
|
|
// simple objects, e.g. mORMot's TOrm
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
|
|
/// an interface used by TAutoLocker to protect multi-thread execution
|
|
IAutoLocker = interface
|
|
['{97559643-6474-4AD3-AF72-B9BB84B4955D}']
|
|
/// enter the mutex
|
|
// - any call to Enter should be ended with a call to Leave, and
|
|
// protected by a try..finally block, as such:
|
|
// !begin
|
|
// ! ... // unsafe code
|
|
// ! fSharedAutoLocker.Enter;
|
|
// ! try
|
|
// ! ... // thread-safe code
|
|
// ! finally
|
|
// ! fSharedAutoLocker.Leave;
|
|
// ! end;
|
|
// !end;
|
|
procedure Enter;
|
|
/// leave the mutex
|
|
// - any call to Leave should be preceded with a call to Enter
|
|
procedure Leave;
|
|
/// will enter the mutex until the IUnknown reference is released
|
|
// - using an IUnknown interface to let the compiler auto-generate a
|
|
// try..finally block statement to release the lock for the code block
|
|
// - could be used as such under Delphi:
|
|
// !begin
|
|
// ! ... // unsafe code
|
|
// ! fSharedAutoLocker.ProtectMethod;
|
|
// ! ... // thread-safe code
|
|
// !end; // local hidden IUnknown will release the lock for the method
|
|
// - warning: under FPC, you should assign its result to a local variable -
|
|
// see bug http://bugs.freepascal.org/view.php?id=26602
|
|
// !var
|
|
// ! LockFPC: IUnknown;
|
|
// !begin
|
|
// ! ... // unsafe code
|
|
// ! LockFPC := fSharedAutoLocker.ProtectMethod;
|
|
// ! ... // thread-safe code
|
|
// !end; // LockFPC will release the lock for the method
|
|
// or
|
|
// !begin
|
|
// ! ... // unsafe code
|
|
// ! with fSharedAutoLocker.ProtectMethod do
|
|
// ! begin
|
|
// ! ... // thread-safe code
|
|
// ! end; // local hidden IUnknown will release the lock for the method
|
|
// !end;
|
|
function ProtectMethod: IUnknown;
|
|
/// gives an access to the internal low-level TSynLocker instance used
|
|
function Safe: PSynLocker;
|
|
end;
|
|
|
|
/// reference-counted block code critical section
|
|
// - you can use one instance of this to protect multi-threaded execution
|
|
// - the main class may initialize a IAutoLocker property in Create, then call
|
|
// IAutoLocker.ProtectMethod in any method to make its execution thread safe
|
|
// - this class inherits from TInterfacedObjectWithCustomCreate so you
|
|
// could define one published property of a mormot.core.interface.pas
|
|
// TInjectableObject as IAutoLocker so that this class may be automatically
|
|
// injected
|
|
// - consider inherit from high-level TSynPersistentLock or call low-level
|
|
// fSafe := NewSynLocker / fSafe^.DoneAndFreemem instead
|
|
TAutoLocker = class(TInterfacedObjectWithCustomCreate, IAutoLocker)
|
|
protected
|
|
fSafe: TSynLocker;
|
|
public
|
|
/// initialize the mutex
|
|
constructor Create; override;
|
|
/// finalize the mutex
|
|
destructor Destroy; override;
|
|
/// will enter the mutex until the IUnknown reference is released
|
|
// - as expected by IAutoLocker interface
|
|
// - could be used as such under Delphi:
|
|
// !begin
|
|
// ! ... // unsafe code
|
|
// ! fSharedAutoLocker.ProtectMethod;
|
|
// ! ... // thread-safe code
|
|
// !end; // local hidden IUnknown will release the lock for the method
|
|
// - warning: under FPC, you should assign its result to a local variable -
|
|
// see bug http://bugs.freepascal.org/view.php?id=26602
|
|
// !var
|
|
// ! LockFPC: IUnknown;
|
|
// !begin
|
|
// ! ... // unsafe code
|
|
// ! LockFPC := fSharedAutoLocker.ProtectMethod;
|
|
// ! ... // thread-safe code
|
|
// !end; // LockFPC will release the lock for the method
|
|
// or
|
|
// !begin
|
|
// ! ... // unsafe code
|
|
// ! with fSharedAutoLocker.ProtectMethod do
|
|
// ! begin
|
|
// ! ... // thread-safe code
|
|
// ! end; // local hidden IUnknown will release the lock for the method
|
|
// !end;
|
|
function ProtectMethod: IUnknown;
|
|
/// enter the mutex
|
|
// - as expected by IAutoLocker interface
|
|
// - any call to Enter should be ended with a call to Leave, and
|
|
// protected by a try..finally block, as such:
|
|
// !begin
|
|
// ! ... // unsafe code
|
|
// ! fSharedAutoLocker.Enter;
|
|
// ! try
|
|
// ! ... // thread-safe code
|
|
// ! finally
|
|
// ! fSharedAutoLocker.Leave;
|
|
// ! end;
|
|
// !end;
|
|
procedure Enter; virtual;
|
|
/// leave the mutex
|
|
// - as expected by IAutoLocker interface
|
|
procedure Leave; virtual;
|
|
/// access to the locking methods of this instance
|
|
// - as expected by IAutoLocker interface
|
|
function Safe: PSynLocker;
|
|
/// direct access to the locking methods of this instance
|
|
// - faster than IAutoLocker.Safe function
|
|
property Locker: TSynLocker
|
|
read fSafe;
|
|
end;
|
|
|
|
|
|
|
|
{ ************ TSynPersistent* TSyn*List TSynLocker classes }
|
|
|
|
type
|
|
/// our own empowered TPersistent-like parent class
|
|
// - TPersistent has an unexpected speed overhead due a giant lock introduced
|
|
// to manage property name fixup resolution (which we won't use outside the UI)
|
|
// - this class has a virtual constructor, so is a preferred alternative
|
|
// to both TPersistent and TPersistentWithCustomCreate classes
|
|
// - features some protected methods to customize its JSON serialization
|
|
// - for best performance, any type inheriting from this class will bypass
|
|
// some regular steps: do not implement interfaces or use TMonitor with them!
|
|
TSynPersistent = class(TObjectWithCustomCreate)
|
|
protected
|
|
// this default implementation will call AssignError()
|
|
procedure AssignTo(Dest: TSynPersistent); virtual;
|
|
procedure AssignError(Source: TSynPersistent);
|
|
public
|
|
/// allows to implement a TPersistent-like assignement mechanism
|
|
// - inherited class should override AssignTo() protected method
|
|
// to implement the proper assignment
|
|
procedure Assign(Source: TSynPersistent); virtual;
|
|
end;
|
|
|
|
/// used to determine the exact class type of a TSynPersistent
|
|
TSynPersistentClass = class of TSynPersistent;
|
|
|
|
|
|
{$ifdef HASITERATORS}
|
|
/// abstract pointer Enumerator
|
|
TPointerEnumerator = record
|
|
private
|
|
Curr, After: PPointer;
|
|
function GetCurrent: pointer; inline;
|
|
public
|
|
procedure Init(Values: PPointerArray; Count: PtrUInt); inline;
|
|
function MoveNext: Boolean; inline;
|
|
function GetEnumerator: TPointerEnumerator; inline;
|
|
/// returns the current pointer value
|
|
property Current: pointer
|
|
read GetCurrent;
|
|
end;
|
|
{$endif HASITERATORS}
|
|
|
|
{$M+}
|
|
/// simple and efficient TList, without any notification
|
|
// - regular TList has an internal notification mechanism which slows down
|
|
// basic process, and can't be easily inherited
|
|
// - stateless methods (like Add/Clear/Exists/Remove) are defined as virtual
|
|
// since can be overriden e.g. by TSynObjectListLocked to add a TSynLocker
|
|
TSynList = class(TObject)
|
|
protected
|
|
fCount: integer;
|
|
fList: TPointerDynArray;
|
|
function Get(index: integer): pointer;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
public
|
|
/// virtual constructor called at instance creation
|
|
constructor Create; virtual;
|
|
/// add one item to the list
|
|
function Add(item: pointer): PtrInt; virtual;
|
|
/// insert one item to the list at a given position
|
|
function Insert(item: pointer; index: PtrInt): PtrInt;
|
|
/// delete all items of the list
|
|
procedure Clear; virtual;
|
|
/// delete one item from the list
|
|
procedure Delete(index: integer; dontfree: boolean = false); virtual;
|
|
/// fast retrieve one item in the list
|
|
function IndexOf(item: pointer): PtrInt; virtual;
|
|
/// fast check if one item exists in the list
|
|
function Exists(item: pointer): boolean; virtual;
|
|
/// fast delete one item in the list
|
|
function Remove(item: pointer): PtrInt; virtual;
|
|
{$ifdef HASITERATORS}
|
|
/// an enumerator able to compile "for .. in list do" statements
|
|
function GetEnumerator: TPointerEnumerator;
|
|
{$endif HASITERATORS}
|
|
/// how many items are stored in this TList instance
|
|
property Count: integer
|
|
read fCount;
|
|
/// low-level access to the items stored in this TList instance
|
|
property List: TPointerDynArray
|
|
read fList;
|
|
/// low-level array-like access to the items stored in this TList instance
|
|
// - warning: if index is out of range, will return nil and won't raise
|
|
// any exception
|
|
property Items[index: integer]: pointer
|
|
read Get; default;
|
|
end;
|
|
{$M-}
|
|
PSynList = ^TSynList;
|
|
|
|
/// simple and efficient TObjectList, without any notification
|
|
TSynObjectList = class(TSynList)
|
|
protected
|
|
fOwnObjects: boolean;
|
|
fItemClass: TClass;
|
|
public
|
|
/// initialize the object list
|
|
// - can optionally specify an item class for efficient JSON serialization
|
|
constructor Create(aOwnObjects: boolean = true;
|
|
aItemClass: TClass = nil); reintroduce; virtual;
|
|
/// delete one object from the list
|
|
// - will also Free the item if OwnObjects was set, and dontfree is false
|
|
procedure Delete(index: integer; dontfree: boolean = false); override;
|
|
/// delete all objects of the list
|
|
procedure Clear; override;
|
|
/// delete all objects of the list in reverse order
|
|
// - for some kind of processes, owned objects should be removed from the
|
|
// last added to the first
|
|
// - will use slower but safer FreeAndNilSafe() instead of plain Free
|
|
procedure ClearFromLast; virtual;
|
|
/// finalize the store items
|
|
destructor Destroy; override;
|
|
/// create a new ItemClass instance, Add() it and return it
|
|
function NewItem: pointer;
|
|
/// optional class of the stored items
|
|
// - used e.g. by _JL_TSynObjectList() when unserializing from JSON
|
|
property ItemClass: TClass
|
|
read fItemClass write fItemClass;
|
|
/// flag set if this list will Free its items on Delete/Clear/Destroy
|
|
property OwnObjects: boolean
|
|
read fOwnObjects write fOwnObjects;
|
|
end;
|
|
PSynObjectList = ^TSynObjectList;
|
|
|
|
/// meta-class of TSynObjectList type
|
|
TSynObjectListClass = class of TSynObjectList;
|
|
|
|
/// adding locking methods to a TSynPersistent with virtual constructor
|
|
// - you may use this class instead of the RTL TCriticalSection, since it
|
|
// would use a TSynLocker which does not suffer from CPU cache line conflit,
|
|
// and is cross-compiler whereas TMonitor is Delphi-specific and buggy (at
|
|
// least before XE5)
|
|
// - if you don't need TSynPersistent overhead, consider plain TSynLocked class
|
|
TSynPersistentLock = class(TSynPersistent)
|
|
protected
|
|
// TSynLocker would increase inherited fields offset -> managed PSynLocker
|
|
fSafe: PSynLocker;
|
|
// will lock/unlock the instance during JSON serialization of its properties
|
|
function RttiBeforeWriteObject(W: TTextWriter;
|
|
var Options: TTextWriterWriteObjectOptions): boolean; override;
|
|
procedure RttiAfterWriteObject(W: TTextWriter;
|
|
Options: TTextWriterWriteObjectOptions); override;
|
|
// set the rcfHookWrite flag to call RttiBeforeWriteObject
|
|
class procedure RttiCustomSetParser(Rtti: TRttiCustom); override;
|
|
public
|
|
/// initialize the instance, and its associated lock
|
|
constructor Create; override;
|
|
/// finalize the instance, and its associated lock
|
|
destructor Destroy; override;
|
|
/// access to the associated instance critical section
|
|
// - call Safe.Lock/UnLock to protect multi-thread access on this storage
|
|
property Safe: PSynLocker
|
|
read fSafe;
|
|
/// could be used as a short-cut to Safe.Lock
|
|
procedure Lock;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// could be used as a short-cut to Safe.UnLock
|
|
procedure Unlock;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
end;
|
|
|
|
/// adding light non-upgradable multiple Read / exclusive Write locking
|
|
// methods to a TSynPersistent with virtual constructor
|
|
TSynPersistentRWLightLock = class(TSynPersistent)
|
|
protected
|
|
fSafe: TRWLightLock;
|
|
public
|
|
/// access to the associated non-upgradable TRWLightLock instance
|
|
// - call Safe methods to protect multi-thread access on this storage
|
|
property Safe: TRWLightLock
|
|
read fSafe;
|
|
end;
|
|
|
|
/// adding light upgradable multiple Read / exclusive Write locking methods
|
|
// to a TSynPersistent with virtual constructor
|
|
TSynPersistentRWLock = class(TSynPersistent)
|
|
protected
|
|
fSafe: TRWLock;
|
|
public
|
|
/// access to the associated upgradable TRWLock instance
|
|
// - call Safe methods to protect multi-thread access on this storage
|
|
property Safe: TRWLock
|
|
read fSafe;
|
|
end;
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
|
|
/// used for backward compatibility only with existing code
|
|
TSynPersistentLocked = class(TSynPersistentLock);
|
|
|
|
{$endif PUREMORMOT2}
|
|
|
|
/// adding locking methods to a TInterfacedObject with virtual constructor
|
|
TInterfacedObjectLocked = class(TInterfacedObjectWithCustomCreate)
|
|
protected
|
|
fSafe: PSynLocker; // TSynLocker would increase inherited fields offset
|
|
public
|
|
/// initialize the object instance, and its associated lock
|
|
constructor Create; override;
|
|
/// release the instance (including the locking resource)
|
|
destructor Destroy; override;
|
|
/// access to the locking methods of this instance
|
|
// - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block
|
|
property Safe: PSynLocker
|
|
read fSafe;
|
|
end;
|
|
|
|
/// adding light locking methods to a TInterfacedObject with virtual constructor
|
|
TInterfacedObjectRWLocked = class(TInterfacedObjectWithCustomCreate)
|
|
protected
|
|
fSafe: TRWLock;
|
|
public
|
|
/// access to the multiple Read / exclusive Write locking methods of this instance
|
|
property Safe: TRWLock
|
|
read fSafe;
|
|
end;
|
|
|
|
/// add TRWLightLock non-upgradable methods to a TSynObjectList
|
|
// - this class expands the regular TSynObjectList to include a TRWLightLock
|
|
// - you need to call the Safe locking methods by hand to protect the
|
|
// execution of all methods, since even Add/Clear/ClearFromLast/Remove/Exists
|
|
// have not been overriden because TRWLighLock.WriteLock is not reentrant
|
|
TSynObjectListLightLocked = class(TSynObjectList)
|
|
protected
|
|
fSafe: TRWLightLock;
|
|
public
|
|
/// the light single Read / exclusive Write LightLock associated to this list
|
|
// - could be used to protect shared resources within the internal process,
|
|
// for index-oriented methods like Delete/Items/Count...
|
|
// - use Safe LightLock methods with a try ... finally bLightLock
|
|
property Safe: TRWLightLock
|
|
read fSafe;
|
|
end;
|
|
|
|
/// add TRWLock upgradable methods to a TSynObjectList
|
|
// - this class expands the regular TSynObjectList to include a TRWLock
|
|
// - you need to call the Safe locking methods by hand to protect the
|
|
// execution of index-oriented methods (like Delete/Items/Count...): the
|
|
// list content may change in the background, so using indexes is thread-safe
|
|
// - on the other hand, Add/Clear/ClearFromLast/Remove stateless methods have
|
|
// been overriden in this class to call Safe lock methods, and therefore are
|
|
// thread-safe and protected to any background change
|
|
TSynObjectListLocked = class(TSynObjectList)
|
|
protected
|
|
fSafe: TRWLock;
|
|
public
|
|
/// add one item to the list using Safe.WriteLock
|
|
function Add(item: pointer): PtrInt; override;
|
|
/// delete all items of the list using Safe.WriteLock
|
|
procedure Clear; override;
|
|
/// delete all items of the list in reverse order, using Safe.WriteLock
|
|
procedure ClearFromLast; override;
|
|
/// fast delete one item in the list, using Safe.WriteLock
|
|
function Remove(item: pointer): PtrInt; override;
|
|
/// check an item using Safe.ReadOnlyLock
|
|
function Exists(item: pointer): boolean; override;
|
|
/// the light single Read / exclusive Write lock associated to this list
|
|
// - could be used to protect shared resources within the internal process,
|
|
// for index-oriented methods like Delete/Items/Count...
|
|
// - use Safe lock methods within a try ... finally block
|
|
property Safe: TRWLock
|
|
read fSafe;
|
|
end;
|
|
|
|
/// event used by TSynObjectListSorted to compare its instances
|
|
TOnObjectCompare = function(A, B: TObject): integer;
|
|
|
|
/// an ordered thread-safe TSynObjectList
|
|
// - items will be stored in order, for O(log(n)) fast search
|
|
TSynObjectListSorted = class(TSynObjectListLocked)
|
|
protected
|
|
fCompare: TOnObjectCompare;
|
|
// returns TRUE and the index of existing Item, or FALSE and the index
|
|
// where the Item is to be inserted so that the array remains sorted
|
|
function Locate(item: pointer; out index: PtrInt): boolean;
|
|
public
|
|
/// initialize the object list to be sorted with the supplied function
|
|
constructor Create(const aCompare: TOnObjectCompare;
|
|
aOwnsObjects: boolean = true); reintroduce;
|
|
/// add in-order one item to the list using Safe.WriteLock
|
|
// - returns the sorted index when item was inserted
|
|
// - returns < 0 if item was found, as -(existingindex + 1)
|
|
function Add(item: pointer): PtrInt; override;
|
|
/// fast retrieve one item in the list using O(log(n)) binary search
|
|
// - this overriden version won't search for the item pointer itself,
|
|
// but will use the Compare() function until it is 0
|
|
function IndexOf(item: pointer): PtrInt; override;
|
|
/// fast retrieve one item in the list using O(log(n)) binary search
|
|
// - supplied item should have enough information for fCompare to work
|
|
function Find(item: TObject): TObject;
|
|
/// how two stored objects are stored
|
|
property Compare: TOnObjectCompare
|
|
read fCompare write fCompare;
|
|
end;
|
|
|
|
|
|
{ ************ TSynPersistentStore with proper Binary Serialization }
|
|
|
|
type
|
|
/// abstract high-level handling of (SynLZ-)compressed persisted storage
|
|
// - LoadFromReader/SaveToWriter abstract methods should be overriden
|
|
// with proper binary persistence implementation
|
|
TSynPersistentStore = class(TSynPersistentRWLock)
|
|
protected
|
|
fName: RawUtf8;
|
|
fReader: TFastReader;
|
|
fReaderTemp: PRawByteString;
|
|
fLoadFromLastUncompressed, fSaveToLastUncompressed: integer;
|
|
fLoadFromLastAlgo: TAlgoCompress;
|
|
/// low-level virtual methods implementing the persistence reading
|
|
procedure LoadFromReader; virtual;
|
|
procedure SaveToWriter(aWriter: TBufferWriter); virtual;
|
|
public
|
|
/// initialize a void storage with the supplied name
|
|
constructor Create(const aName: RawUtf8); reintroduce; overload; virtual;
|
|
/// initialize a storage from a SaveTo persisted buffer
|
|
// - raise a EFastReader exception on decoding error
|
|
constructor CreateFrom(const aBuffer: RawByteString;
|
|
aLoad: TAlgoCompressLoad = aclNormal);
|
|
/// initialize a storage from a SaveTo persisted buffer
|
|
// - raise a EFastReader exception on decoding error
|
|
constructor CreateFromBuffer(aBuffer: pointer; aBufferLen: integer;
|
|
aLoad: TAlgoCompressLoad = aclNormal);
|
|
/// initialize a storage from a SaveTo persisted buffer
|
|
// - raise a EFastReader exception on decoding error
|
|
constructor CreateFromFile(const aFileName: TFileName;
|
|
aLoad: TAlgoCompressLoad = aclNormal);
|
|
/// fill the storage from a SaveTo persisted buffer
|
|
// - actually call the LoadFromReader() virtual method for persistence
|
|
// - raise a EFastReader exception on decoding error
|
|
procedure LoadFrom(const aBuffer: RawByteString;
|
|
aLoad: TAlgoCompressLoad = aclNormal); overload;
|
|
/// initialize the storage from a SaveTo persisted buffer
|
|
// - actually call the LoadFromReader() virtual method for persistence
|
|
// - raise a EFastReader exception on decoding error
|
|
procedure LoadFrom(aBuffer: pointer; aBufferLen: integer;
|
|
aLoad: TAlgoCompressLoad = aclNormal); overload; virtual;
|
|
/// initialize the storage from a SaveToFile content
|
|
// - actually call the LoadFromReader() virtual method for persistence
|
|
// - returns false if the file is not found, true if the file was loaded
|
|
// without any problem, or raise a EFastReader exception on decoding error
|
|
function LoadFromFile(const aFileName: TFileName;
|
|
aLoad: TAlgoCompressLoad = aclNormal): boolean;
|
|
/// persist the content as a SynLZ-compressed binary blob
|
|
// - to be retrieved later on via LoadFrom method
|
|
// - actually call the SaveToWriter() protected virtual method for persistence
|
|
// - you can specify ForcedAlgo if you want to override the default AlgoSynLZ
|
|
// - BufferOffset could be set to reserve some bytes before the compressed buffer
|
|
procedure SaveTo(out aBuffer: RawByteString; nocompression: boolean = false;
|
|
BufLen: integer = 65536; ForcedAlgo: TAlgoCompress = nil;
|
|
BufferOffset: integer = 0); overload; virtual;
|
|
/// persist the content as a SynLZ-compressed binary blob
|
|
// - just an overloaded wrapper
|
|
function SaveTo(nocompression: boolean = false; BufLen: integer = 65536;
|
|
ForcedAlgo: TAlgoCompress = nil; BufferOffset: integer = 0): RawByteString; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// persist the content as a SynLZ-compressed binary file
|
|
// - to be retrieved later on via LoadFromFile method
|
|
// - returns the number of bytes of the resulting file
|
|
// - actually call the SaveTo method for persistence
|
|
function SaveToFile(const aFileName: TFileName; nocompression: boolean = false;
|
|
BufLen: integer = 65536; ForcedAlgo: TAlgoCompress = nil): PtrUInt;
|
|
/// one optional text associated with this storage
|
|
// - you can define this field as published to serialize its value in log/JSON
|
|
property Name: RawUtf8
|
|
read fName;
|
|
/// after a LoadFrom(), contains the uncompressed data size read
|
|
property LoadFromLastUncompressed: integer
|
|
read fLoadFromLastUncompressed;
|
|
/// after a SaveTo(), contains the uncompressed data size written
|
|
property SaveToLastUncompressed: integer
|
|
read fSaveToLastUncompressed;
|
|
end;
|
|
|
|
|
|
|
|
{ ********** Efficient RTTI Values Binary Serialization and Comparison }
|
|
|
|
type
|
|
/// possible options for a TDocVariant JSON/BSON document storage
|
|
// - defined in this unit to avoid circular reference with mormot.core.variants
|
|
// - dvoIsArray and dvoIsObject will store the "Kind: TDocVariantKind" state -
|
|
// you should never have to define these two options directly
|
|
// - dvoNameCaseSensitive will be used for every name lookup - here
|
|
// case-insensitivity is restricted to a-z A-Z 0-9 and _ characters
|
|
// - dvoCheckForDuplicatedNames will be used for method
|
|
// TDocVariantData.AddValue(), but not when setting properties at
|
|
// variant level: for consistency, "aVariant.AB := aValue" will replace
|
|
// any previous value for the name "AB"
|
|
// - dvoReturnNullForUnknownProperty will be used when retrieving any value
|
|
// from its name (for dvObject kind of instance), or index (for dvArray or
|
|
// dvObject kind of instance)
|
|
// - by default, internal values will be copied by-value from one variant
|
|
// instance to another, to ensure proper safety - but it may be too slow:
|
|
// if you set dvoValueCopiedByReference, the internal
|
|
// TDocVariantData.VValue/VName instances will be copied by-reference,
|
|
// to avoid memory allocations, BUT it may break internal process if you change
|
|
// some values in place (since VValue/VName and VCount won't match) - as such,
|
|
// if you set this option, ensure that you use the content as read-only
|
|
// - any registered custom types may have an extended JSON syntax (e.g.
|
|
// TBsonVariant does for MongoDB types), and will be searched during JSON
|
|
// parsing, unless dvoJsonParseDoNotTryCustomVariants is set (slightly faster)
|
|
// - the parser will try to guess the array or object size by pre-fetching
|
|
// some content: you can set dvoJsonParseDoNotGuessCount if your input has
|
|
// a lot of nested documents, and manual resize is preferred - this option
|
|
// will be forced by InitJson if a huge nest of objects is detected
|
|
// - by default, it will only handle direct JSON [array] of {object}: but if
|
|
// you define dvoJsonObjectParseWithinString, it will also try to un-escape
|
|
// a JSON string first, i.e. handle "[array]" or "{object}" content (may be
|
|
// used e.g. when JSON has been retrieved from a database TEXT column) - is
|
|
// used for instance by VariantLoadJson()
|
|
// - JSON serialization will follow the standard layout, unless
|
|
// dvoSerializeAsExtendedJson is set so that the property names would not
|
|
// be escaped with double quotes, writing '{name:"John",age:123}' instead of
|
|
// '{"name":"John","age":123}': this extended json layout is compatible with
|
|
// http://docs.mongodb.org/manual/reference/mongodb-extended-json and with
|
|
// TDocVariant JSON unserialization, also our SynCrossPlatformJSON unit, but
|
|
// NOT recognized by most JSON clients, like AJAX/JavaScript or C#/Java
|
|
// - by default, only integer/Int64/currency number values are allowed, unless
|
|
// dvoAllowDoubleValue is set and 32-bit floating-point conversion is tried,
|
|
// with potential loss of precision during the conversion
|
|
// - dvoInternNames and dvoInternValues will use shared TRawUtf8Interning
|
|
// instances to maintain a list of RawUtf8 names/values for all TDocVariant,
|
|
// so that redundant text content will be allocated only once on heap
|
|
// - see JSON_[TDocVariantModel] and all JSON_* constants as useful sets
|
|
TDocVariantOption = (
|
|
dvoIsArray,
|
|
dvoIsObject,
|
|
dvoNameCaseSensitive,
|
|
dvoCheckForDuplicatedNames,
|
|
dvoReturnNullForUnknownProperty,
|
|
dvoValueCopiedByReference,
|
|
dvoJsonParseDoNotTryCustomVariants,
|
|
dvoJsonParseDoNotGuessCount,
|
|
dvoJsonObjectParseWithinString,
|
|
dvoSerializeAsExtendedJson,
|
|
dvoAllowDoubleValue,
|
|
dvoInternNames,
|
|
dvoInternValues);
|
|
|
|
/// set of options for a TDocVariant storage
|
|
// - defined in this unit to avoid circular reference with mormot.core.variants
|
|
// - see JSON_[TDocVariantModel] and all JSON_* constants (e.g. JSON_FAST or
|
|
// JSON_FAST_FLOAT) as potential values
|
|
// - when specifying the options, you should not include dvoIsArray nor
|
|
// dvoIsObject directly in the set, but explicitly define TDocVariantDataKind
|
|
TDocVariantOptions = set of TDocVariantOption;
|
|
|
|
/// pointer to a set of options for a TDocVariant storage
|
|
// - defined in this unit to avoid circular reference with mormot.core.variants
|
|
// - use e.g. @JSON_[mFast], @JSON_[mDefault], or any other TDocVariantModel
|
|
PDocVariantOptions = ^TDocVariantOptions;
|
|
|
|
/// a boolean array of TDocVariant storage options
|
|
TDocVariantOptionsBool = array[boolean] of TDocVariantOptions;
|
|
PDocVariantOptionsBool = ^TDocVariantOptionsBool;
|
|
|
|
|
|
type
|
|
/// internal function handler for binary persistence of any RTTI type value
|
|
// - i.e. the kind of functions called via RTTI_BINARYSAVE[] lookup table
|
|
// - work with managed and unmanaged types
|
|
// - persist Data^ into Dest, returning the size in Data^ as bytes
|
|
TRttiBinarySave = function(Data: pointer; Dest: TBufferWriter;
|
|
Info: PRttiInfo): PtrInt;
|
|
|
|
/// the type of RTTI_BINARYSAVE[] efficient lookup table
|
|
TRttiBinarySaves = array[TRttiKind] of TRttiBinarySave;
|
|
PRttiBinarySaves = ^TRttiBinarySaves;
|
|
|
|
/// internal function handler for binary persistence of any RTTI type value
|
|
// - i.e. the kind of functions called via RTTI_BINARYLOAD[] lookup table
|
|
// - work with managed and unmanaged types
|
|
// - fill Data^ from Source, returning the size in Data^ as bytes
|
|
TRttiBinaryLoad = function(Data: pointer; var Source: TFastReader;
|
|
Info: PRttiInfo): PtrInt;
|
|
|
|
/// the type of RTTI_BINARYLOAD[] efficient lookup table
|
|
TRttiBinaryLoads = array[TRttiKind] of TRttiBinaryLoad;
|
|
PRttiBinaryLoads = ^TRttiBinaryLoads;
|
|
|
|
/// internal function handler for fast comparison of any RTTI type value
|
|
// - i.e. the kind of functions called via RTTI_COMPARE[] lookup table
|
|
// - work with managed and unmanaged types
|
|
// - returns the size in Data1/Data2^ as bytes, and the result in Compared
|
|
TRttiCompare = function(Data1, Data2: pointer; Info: PRttiInfo;
|
|
out Compared: integer): PtrInt;
|
|
|
|
/// the type of RTTI_COMPARE[] efficient lookup table
|
|
TRttiCompares = array[TRttiKind] of TRttiCompare;
|
|
PRttiCompares = ^TRttiCompares;
|
|
|
|
TRttiComparers = array[{CaseInSensitive=}boolean] of TRttiCompares;
|
|
|
|
var
|
|
/// lookup table for binary persistence of any RTTI type value
|
|
// - for efficient persistence into binary of managed and unmanaged types
|
|
RTTI_BINARYSAVE: TRttiBinarySaves;
|
|
|
|
/// lookup table for binary persistence of any RTTI type value
|
|
// - for efficient retrieval from binary of managed and unmanaged types
|
|
RTTI_BINARYLOAD: TRttiBinaryLoads;
|
|
|
|
/// lookup table for comparison of any RTTI type value
|
|
// - for efficient search or sorting of managed and unmanaged types
|
|
// - RTTI_COMPARE[false] for case-sensitive comparison
|
|
// - RTTI_COMPARE[true] for case-insensitive comparison
|
|
RTTI_COMPARE: TRttiComparers;
|
|
|
|
/// lookup table for comparison of ordinal RTTI type values
|
|
// - slightly faster alternative to RTTI_COMPARE[rkOrdinalTypes]
|
|
RTTI_ORD_COMPARE: array[TRttiOrd] of TRttiCompare;
|
|
|
|
/// lookup table for comparison of floating-point RTTI type values
|
|
// - slightly faster alternative to RTTI_COMPARE[rkFloat]
|
|
RTTI_FLOAT_COMPARE: array[TRttiFloat] of TRttiCompare;
|
|
|
|
/// raw binary serialization of a dynamic array
|
|
// - as called e.g. by TDynArray.SaveTo, using ExternalCount optional parameter
|
|
// - RTTI_BINARYSAVE[rkDynArray] is a wrapper to this function, with ExternalCount=nil
|
|
procedure DynArraySave(Data: PAnsiChar; ExternalCount: PInteger;
|
|
Dest: TBufferWriter; Info: PRttiInfo); overload;
|
|
|
|
/// serialize a dynamic array content as binary, ready to be loaded by
|
|
// DynArrayLoad() / TDynArray.Load()
|
|
// - Value shall be set to the source dynamic arry field
|
|
// - is a wrapper around BinarySave(rkDynArray)
|
|
function DynArraySave(var Value; TypeInfo: PRttiInfo): RawByteString; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// fill a dynamic array content from a binary serialization as saved by
|
|
// DynArraySave() / TDynArray.Save()
|
|
// - Value shall be set to the target dynamic array field
|
|
// - is a wrapper around BinaryLoad(rkDynArray)
|
|
function DynArrayLoad(var Value; Source: PAnsiChar; TypeInfo: PRttiInfo;
|
|
{$ifdef PUREMORMOT2} // SourceMax is manadatory for safety
|
|
TryCustomVariants: PDocVariantOptions; SourceMax: PAnsiChar): PAnsiChar;
|
|
{$else}
|
|
TryCustomVariants: PDocVariantOptions = nil; SourceMax: PAnsiChar = nil): PAnsiChar;
|
|
{$endif PUREMORMOT2}
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// low-level binary unserialization as saved by DynArraySave/TDynArray.Save
|
|
// - as used by DynArrayLoad() and TDynArrayLoadFrom
|
|
// - returns the stored length() of the dynamic array, and Source points to
|
|
// the stored binary data itself
|
|
function DynArrayLoadHeader(var Source: TFastReader;
|
|
ArrayInfo, ItemInfo: PRttiInfo): integer;
|
|
|
|
/// raw comparison of two dynamic arrays
|
|
// - as called e.g. by TDynArray.Equals, using ExternalCountA/B optional parameter
|
|
// - RTTI_COMPARE[true/false,rkDynArray] are wrappers to this, with ExternalCount=nil
|
|
// - if Info=TypeInfo(TObjectDynArray) then will compare any T*ObjArray
|
|
function DynArrayCompare(A, B: PAnsiChar; ExternalCountA, ExternalCountB: PInteger;
|
|
Info: PRttiInfo; CaseInSensitive: boolean): integer; overload;
|
|
|
|
/// wrapper around TDynArray.Add
|
|
// - warning: the Item type is not checked at runtime, so should be as expected
|
|
// - not very fast, but could be useful for simple code
|
|
function DynArrayAdd(TypeInfo: PRttiInfo; var DynArray; const Item): integer; overload;
|
|
|
|
/// wrapper around TDynArray.Delete
|
|
// - not very fast, but could be useful for simple code
|
|
function DynArrayDelete(TypeInfo: PRttiInfo; var DynArray; Index: PtrInt): boolean; overload;
|
|
|
|
/// compare two dynamic arrays by calling TDynArray.Equals
|
|
// - if Info=TypeInfo(TObjectDynArray) then will compare any T*ObjArray
|
|
function DynArrayEquals(TypeInfo: PRttiInfo; var Array1, Array2;
|
|
Array1Count: PInteger = nil; Array2Count: PInteger = nil;
|
|
CaseInsensitive: boolean = false): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
{$ifdef FPCGENERICS}
|
|
/// wrapper around TDynArray.Add
|
|
// - warning: the Item type is not checked at runtime, so should be as expected
|
|
// - not very fast, but could be useful for simple code
|
|
function DynArrayAdd<TArray>(var DynArray: TArray; const Item): integer; overload;
|
|
|
|
/// wrapper around TDynArray.Delete
|
|
// - not very fast, but could be useful for simple code
|
|
function DynArrayDelete<TArray>(var DynArray: TArray; Index: PtrInt): boolean; overload;
|
|
|
|
/// compare two dynamic arrays values
|
|
function DynArrayCompare<TArray>(var Array1, Array2: TArray;
|
|
CaseInSensitive: boolean = false): integer; overload;
|
|
{$endif FPCGENERICS}
|
|
|
|
// some low-level comparison methods used by mormot.core.json
|
|
function _BC_SQWord(A, B: PInt64; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
function _BC_UQWord(A, B: PQWord; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
function _BC_ObjArray(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
function _BCI_ObjArray(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
|
|
/// check equality of two values by content, using RTTI
|
|
// - optionally returns the known in-memory PSize of the value
|
|
function BinaryEquals(A, B: pointer; Info: PRttiInfo; PSize: PInteger;
|
|
Kinds: TRttiKinds; CaseInSensitive: boolean): boolean;
|
|
|
|
/// comparison of two values by content, using RTTI
|
|
function BinaryCompare(A, B: pointer; Info: PRttiInfo;
|
|
CaseInSensitive: boolean): integer; overload;
|
|
|
|
/// comparison of two arrays of values by content, using RTTI
|
|
function BinaryCompare(A, B: pointer; Info: PRttiInfo; Count: PtrInt;
|
|
CaseInSensitive: boolean): integer; overload;
|
|
|
|
/// comparison of two TObject published properties, using RTTI
|
|
function ObjectCompare(A, B: TObject; CaseInSensitive: boolean): integer; overload;
|
|
|
|
/// comparison of published properties of several TObject instances, using RTTI
|
|
function ObjectCompare(A, B: PObject; Count: PtrInt;
|
|
CaseInsensitive: boolean = false): integer; overload;
|
|
|
|
/// case-sensitive comparison of two TObject published properties, using RTTI
|
|
function ObjectEquals(A, B: TObject): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// case-insensitive comparison of two TObject published properties, using RTTI
|
|
function ObjectEqualsI(A, B: TObject): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
|
|
/// how many bytes a BinarySave() may return
|
|
// - deprecated function - use overloaded BinarySave() functions instead
|
|
function BinarySaveLength(Data: pointer; Info: PRttiInfo; Len: PInteger;
|
|
Kinds: TRttiKinds): integer; deprecated;
|
|
|
|
/// binary persistence of any value using RTTI, into a memory buffer
|
|
// - deprecated function - use overloaded BinarySave() functions instead
|
|
function BinarySave(Data: pointer; Dest: PAnsiChar; Info: PRttiInfo;
|
|
out Len: integer; Kinds: TRttiKinds): PAnsiChar; overload; deprecated;
|
|
|
|
{$endif PUREMORMOT2}
|
|
|
|
/// binary persistence of any value using RTTI, into a RawByteString buffer
|
|
function BinarySave(Data: pointer; Info: PRttiInfo; Kinds: TRttiKinds;
|
|
WithCrc: boolean = false): RawByteString; overload;
|
|
|
|
/// binary persistence of any value using RTTI, into a TBytes buffer
|
|
function BinarySaveBytes(Data: pointer; Info: PRttiInfo; Kinds: TRttiKinds): TBytes;
|
|
|
|
/// binary persistence of any value using RTTI, into a TBufferWriter stream
|
|
procedure BinarySave(Data: pointer; Info: PRttiInfo; Dest: TBufferWriter); overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// binary persistence of any value using RTTI, into a TSynTempBuffer buffer
|
|
procedure BinarySave(Data: pointer; var Dest: TSynTempBuffer;
|
|
Info: PRttiInfo; Kinds: TRttiKinds; WithCrc: boolean = false); overload;
|
|
|
|
/// binary persistence of any value using RTTI, into a Base64-encoded text
|
|
// - contains a trailing crc32c hash before the actual data
|
|
function BinarySaveBase64(Data: pointer; Info: PRttiInfo; UriCompatible: boolean;
|
|
Kinds: TRttiKinds; WithCrc: boolean = true): RawUtf8;
|
|
|
|
/// unserialize any value from BinarySave() memory buffer, using RTTI
|
|
function BinaryLoad(Data: pointer; Source: PAnsiChar; Info: PRttiInfo;
|
|
Len: PInteger; SourceMax: PAnsiChar; Kinds: TRttiKinds;
|
|
TryCustomVariants: PDocVariantOptions = nil): PAnsiChar; overload;
|
|
|
|
/// unserialize any value from BinarySave() RawByteString, using RTTI
|
|
function BinaryLoad(Data: pointer; const Source: RawByteString; Info: PRttiInfo;
|
|
Kinds: TRttiKinds; TryCustomVariants: PDocVariantOptions = nil): boolean; overload;
|
|
|
|
/// unserialize any value from BinarySaveBase64() encoding, using RTTI
|
|
// - optionally contains a trailing crc32c hash before the actual data
|
|
function BinaryLoadBase64(Source: PAnsiChar; Len: PtrInt; Data: pointer;
|
|
Info: PRttiInfo; UriCompatible: boolean; Kinds: TRttiKinds;
|
|
WithCrc: boolean = true; TryCustomVariants: PDocVariantOptions = nil): boolean;
|
|
|
|
|
|
/// check equality of two records by content
|
|
// - will handle packed records, with binaries (byte, word, integer...) and
|
|
// string types properties
|
|
// - will use binary-level comparison: it could fail to match two floating-point
|
|
// values because of rounding issues (Currency won't have this problem)
|
|
// - is a wrapper around BinaryEquals(rkRecordTypes)
|
|
function RecordEquals(const RecA, RecB; TypeInfo: PRttiInfo;
|
|
PRecSize: PInteger = nil; CaseInSensitive: boolean = false): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// save a record content into a RawByteString
|
|
// - will handle packed records, with binaries (byte, word, integer...) and
|
|
// string types properties (but not with internal raw pointers, of course)
|
|
// - will use a proprietary binary format, with some variable-length encoding
|
|
// of the string length - note that if you change the type definition, any
|
|
// previously-serialized content will fail, maybe triggering unexpected GPF: you
|
|
// may use TypeInfoToHash() if you share this binary data accross executables
|
|
// - warning: will encode RTL string fields as AnsiString (one byte per char)
|
|
// prior to Delphi 2009, and as UnicodeString (two bytes per char) since Delphi
|
|
// 2009: if you want to use this function between UNICODE and NOT UNICODE
|
|
// versions of Delphi, you should use some explicit types like RawUtf8,
|
|
// WinAnsiString, SynUnicode or even RawUnicode/WideString
|
|
// - is a wrapper around BinarySave(rkRecordTypes)
|
|
function RecordSave(const Rec; TypeInfo: PRttiInfo): RawByteString; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// save a record content into a TBytes dynamic array
|
|
// - could be used as an alternative to RawByteString's RecordSave()
|
|
// - is a wrapper around BinarySaveBytes(rkRecordTypes)
|
|
function RecordSaveBytes(const Rec; TypeInfo: PRttiInfo): TBytes;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
|
|
/// compute the number of bytes needed to save a record content
|
|
// using the RecordSave() function
|
|
// - deprecated function - use overloaded BinarySave() functions instead
|
|
function RecordSaveLength(const Rec; TypeInfo: PRttiInfo;
|
|
Len: PInteger = nil): integer; deprecated;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// save a record content into a destination memory buffer
|
|
// - Dest must be at least RecordSaveLength() bytes long
|
|
// - deprecated function - use overloaded BinarySave() functions instead
|
|
function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: PRttiInfo;
|
|
out Len: integer): PAnsiChar; overload; deprecated;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// save a record content into a destination memory buffer
|
|
// - Dest must be at least RecordSaveLength() bytes long
|
|
// - deprecated function - use overloaded BinarySave() functions instead
|
|
function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: PRttiInfo): PAnsiChar;
|
|
overload; deprecated; {$ifdef HASINLINE}inline;{$endif}
|
|
|
|
{$endif PUREMORMOT2}
|
|
|
|
/// save a record content into a destination memory buffer
|
|
// - caller should make Dest.Done once finished with Dest.buf/Dest.len buffer
|
|
// - is a wrapper around BinarySave(rkRecordTypes)
|
|
procedure RecordSave(const Rec; var Dest: TSynTempBuffer; TypeInfo: PRttiInfo); overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// save a record content into a Base64 encoded UTF-8 text content
|
|
// - will use RecordSave() format, with a left-sided binary CRC
|
|
// - is a wrapper around BinarySaveBase64(rkRecordTypes)
|
|
function RecordSaveBase64(const Rec; TypeInfo: PRttiInfo;
|
|
UriCompatible: boolean = false): RawUtf8;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// fill a record content from a memory buffer as saved by RecordSave()
|
|
// - return nil if the Source buffer is incorrect
|
|
// - in case of success, return the memory buffer pointer just after the
|
|
// read content, and set the Rec size, in bytes, into Len reference variable
|
|
// - will use a proprietary binary format, with some variable-length encoding
|
|
// of the string length - note that if you change the type definition, any
|
|
// previously-serialized content will fail, maybe triggering unexpected GPF: you
|
|
// may use TypeInfoToHash() if you share this binary data accross executables
|
|
// - you should provide in SourceMax the first byte after the Source memory
|
|
// buffer, which will be used to avoid any unexpected buffer overflow - clearly
|
|
// mandatory when decoding the content from any external process (e.g. a
|
|
// maybe-forged client) - with no performance penalty
|
|
// - is a wrapper around BinaryLoad(rkRecordTypes)
|
|
function RecordLoad(var Rec; Source: PAnsiChar; TypeInfo: PRttiInfo;
|
|
{$ifdef PUREMORMOT2} // SourceMax is manadatory for safety
|
|
Len: PInteger; SourceMax: PAnsiChar;
|
|
{$else} // mORMot 1 compatibility mode
|
|
Len: PInteger = nil; SourceMax: PAnsiChar = nil;
|
|
{$endif PUREMORMOT2}
|
|
TryCustomVariants: PDocVariantOptions = nil): PAnsiChar; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// fill a record content from a memory buffer as saved by RecordSave()
|
|
// - will use the Source length to detect and avoid any buffer overlow
|
|
// - returns false if the Source buffer was incorrect, true on success
|
|
// - is a wrapper around BinaryLoad(rkRecordTypes)
|
|
function RecordLoad(var Rec; const Source: RawByteString;
|
|
TypeInfo: PRttiInfo; TryCustomVariants: PDocVariantOptions = nil): boolean; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// read a record content from a Base64 encoded content
|
|
// - expects RecordSaveBase64() format, with a left-sided binary CRC32C
|
|
// - is a wrapper around BinaryLoadBase64(rkRecordTypes)
|
|
function RecordLoadBase64(Source: PAnsiChar; Len: PtrInt; var Rec; TypeInfo: PRttiInfo;
|
|
UriCompatible: boolean = false; TryCustomVariants: PDocVariantOptions = nil): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// crc32c-based hash of a variant value
|
|
// - complex string types will make up to 255 uppercase characters conversion
|
|
// if CaseInsensitive is true
|
|
// - you can specify your own hashing function if crc32c is not what you expect
|
|
function VariantHash(const value: variant; CaseInsensitive: boolean;
|
|
Hasher: THasher = nil): cardinal;
|
|
|
|
|
|
{ ************ TDynArray and TDynArrayHashed Wrappers }
|
|
|
|
type
|
|
/// function prototype to be used for TDynArray Sort and Find method
|
|
// - common functions exist for base types: see e.g. SortDynArrayBoolean,
|
|
// SortDynArrayByte, SortDynArrayWord, SortDynArrayInteger, SortDynArrayCardinal,
|
|
// SortDynArrayInt64, SortDynArrayQWord, SordDynArraySingle, SortDynArrayDouble,
|
|
// SortDynArrayAnsiString, SortDynArrayAnsiStringI, SortDynArrayUnicodeString,
|
|
// SortDynArrayUnicodeStringI, SortDynArrayString, SortDynArrayStringI
|
|
// - any custom type (even records) can be compared then sort by defining
|
|
// such a custom function
|
|
// - must return 0 if A=B, -1 if A<B, 1 if A>B
|
|
TDynArraySortCompare = function(const A, B): integer;
|
|
|
|
/// event oriented version of TDynArraySortCompare
|
|
TOnDynArraySortCompare = function(const A, B): integer of object;
|
|
|
|
/// defined here as forward definition of the TRawUtf8Interning final class
|
|
TRawUtf8InterningAbstract = class(TSynPersistent);
|
|
|
|
const
|
|
/// redirect to the proper SortDynArrayAnsiString/SortDynArrayAnsiStringI
|
|
SORT_LSTRING: array[{caseins=}boolean] of TDynArraySortCompare = (
|
|
{$ifdef CPUINTEL}
|
|
SortDynArrayAnsiString,
|
|
{$else}
|
|
SortDynArrayRawByteString,
|
|
{$endif CPUINTEL}
|
|
SortDynArrayAnsiStringI);
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
|
|
type
|
|
/// internal enumeration used to specify some standard arrays
|
|
// - mORMot 1.18 did have two serialization engines - we unified it
|
|
// - defined only for backward compatible code; use TRttiParserType instead
|
|
TDynArrayKind = TRttiParserType;
|
|
TDynArrayKinds = TRttiParserTypes;
|
|
|
|
const
|
|
/// deprecated TDynArrayKind enumerate mapping
|
|
// - defined only for backward compatible code; use TRttiParserType instead
|
|
djNone = ptNone;
|
|
djboolean = ptboolean;
|
|
djByte = ptByte;
|
|
djWord = ptWord;
|
|
djInteger = ptInteger;
|
|
djCardinal = ptCardinal;
|
|
djSingle = ptSingle;
|
|
djInt64 = ptInt64;
|
|
djQWord = ptQWord;
|
|
djDouble = ptDouble;
|
|
djCurrency = ptCurrency;
|
|
djTimeLog = ptTimeLog;
|
|
djDateTime = ptDateTime;
|
|
djDateTimeMS = ptDateTimeMS;
|
|
djRawUtf8 = ptRawUtf8;
|
|
djRawJson = ptRawJson;
|
|
djWinAnsi = ptWinAnsi;
|
|
djString = ptString;
|
|
djRawByteString = ptRawByteString;
|
|
djWideString = ptWideString;
|
|
djSynUnicode = ptSynUnicode;
|
|
djHash128 = ptHash128;
|
|
djHash256 = ptHash256;
|
|
djHash512 = ptHash512;
|
|
djVariant = ptVariant;
|
|
djCustom = ptCustom;
|
|
djPointer = ptPtrInt;
|
|
djObject = ptPtrInt;
|
|
djUnmanagedTypes = ptUnmanagedTypes;
|
|
djStringTypes = ptStringTypes;
|
|
|
|
{$endif PUREMORMOT2}
|
|
|
|
type
|
|
/// the kind of exceptions raised during TDynArray/TDynArrayHashed process
|
|
EDynArray = class(ESynException);
|
|
|
|
/// a pointer to a TDynArray Wrapper instance
|
|
PDynArray = ^TDynArray;
|
|
|
|
/// a wrapper around a dynamic array with one dimension
|
|
// - provide TList-like methods using fast RTTI information
|
|
// - can be used to fast save/retrieve all memory content to a TStream
|
|
// - note that the "const Item" is not checked at compile time nor runtime:
|
|
// you must ensure that Item matchs the element type of the dynamic array;
|
|
// all Item*() methods will use pointers for safety
|
|
// - can use external Count storage to make Add() and Delete() much faster
|
|
// (avoid most reallocation of the memory buffer)
|
|
// - Note that TDynArray is just a wrapper around an existing dynamic array:
|
|
// methods can modify the content of the associated variable but the TDynArray
|
|
// doesn't contain any data by itself. It is therefore aimed to initialize
|
|
// a TDynArray wrapper on need, to access any existing dynamic array.
|
|
// - is defined as an object or as a record, due to a bug
|
|
// in Delphi 2009/2010 compiler (at least): this structure is not initialized
|
|
// if defined as an object on the stack, but will be as a record :(
|
|
{$ifdef UNDIRECTDYNARRAY}
|
|
TDynArray = record
|
|
{$else}
|
|
TDynArray = object
|
|
{$endif UNDIRECTDYNARRAY}
|
|
private
|
|
fValue: PPointer;
|
|
fInfo: TRttiCustom;
|
|
fCountP: PInteger;
|
|
fCompare: TDynArraySortCompare;
|
|
fSorted, fNoFinalize: boolean;
|
|
function GetCount: PtrInt;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
procedure SetCount(aCount: PtrInt);
|
|
function GetCapacity: PtrInt;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
procedure SetCapacity(aCapacity: PtrInt);
|
|
procedure SetCompare(const aCompare: TDynArraySortCompare);
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
function FindIndex(const Item; aIndex: PIntegerDynArray;
|
|
aCompare: TDynArraySortCompare): PtrInt;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// faster than RTL + handle T*ObjArray + ensure unique
|
|
procedure InternalSetLength(OldLength, NewLength: PtrUInt);
|
|
public
|
|
/// initialize the wrapper with a one-dimension dynamic array
|
|
// - the dynamic array must have been defined with its own type
|
|
// (e.g. TIntegerDynArray = array of integer)
|
|
// - if aCountPointer is set, it will be used instead of length() to store
|
|
// the dynamic array items count - it will be much faster when adding
|
|
// items to the array, because the dynamic array won't need to be
|
|
// resized each time - but in this case, you should use the Count property
|
|
// instead of length(array) or high(array) when accessing the data: in fact
|
|
// length(array) will store the memory size reserved, not the items count
|
|
// - if aCountPointer is set, its content will be set to 0, whatever the
|
|
// array length is, or the current aCountPointer^ value is - to bypass this
|
|
// behavior and keep an existing Count, call UseExternalCount() after Init()
|
|
// - a sample usage may be:
|
|
// !var
|
|
// ! DA: TDynArray;
|
|
// ! A: TIntegerDynArray;
|
|
// !begin
|
|
// ! DA.Init(TypeInfo(TIntegerDynArray), A);
|
|
// ! (...)
|
|
// - a sample usage may be (using a count variable):
|
|
// !var
|
|
// ! DA: TDynArray;
|
|
// ! A: TIntegerDynArray;
|
|
// ! ACount: integer;
|
|
// ! i: integer;
|
|
// !begin
|
|
// ! DA.Init(TypeInfo(TIntegerDynArray), A, @ACount);
|
|
// ! for i := 1 to 100000 do
|
|
// ! DA.Add(i); // MUCH faster using the ACount variable
|
|
// ! (...) // now you should use DA.Count or Count instead of length(A)
|
|
procedure Init(aTypeInfo: PRttiInfo; var aValue; aCountPointer: PInteger = nil);
|
|
/// initialize the wrapper with a one-dimension dynamic array
|
|
// - also set the Compare() function from a supplied TRttiParserType
|
|
// - aKind=ptNone will guess the type from Info.ArrayRtti/ArrayFirstField
|
|
// - will raise an exception if there is not enough RTTI available
|
|
// - no RTTI check is made over the corresponding array layout: you shall
|
|
// ensure that the aKind parameter matches at least the first field of
|
|
// the dynamic array item definition
|
|
// - aCaseInsensitive will be used for ptStringTypes
|
|
function InitSpecific(aTypeInfo: PRttiInfo; var aValue; aKind: TRttiParserType;
|
|
aCountPointer: PInteger = nil; aCaseInsensitive: boolean = false): TRttiParserType;
|
|
/// set a specific TRttiParserType for this dynamic array
|
|
// - could be called after InitRtti() to set the Compare() function
|
|
// - as used by InitSpecific() after InitRtti(Rtti.RegisterType(aTypeInfo))
|
|
function SetParserType(aKind: TRttiParserType; aCaseInsensitive: boolean): TRttiParserType;
|
|
/// initialize the wrapper with a one-dimension dynamic array
|
|
// - low-level method, as called by Init() and InitSpecific()
|
|
// - can be called directly for a very fast TDynArray initialization
|
|
// - warning: caller should check that aInfo.Kind=rkDynArray
|
|
procedure InitRtti(aInfo: TRttiCustom; var aValue; aCountPointer: PInteger); overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// initialize the wrapper with a one-dimension dynamic array
|
|
// - low-level method, as called by Init() and InitSpecific()
|
|
// - can be called directly for a very fast TDynArray initialization
|
|
// - warning: caller should check that aInfo.Kind=rkDynArray
|
|
procedure InitRtti(aInfo: TRttiCustom; var aValue); overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// fast initialize a wrapper for an existing dynamic array of the same type
|
|
// - is slightly faster than
|
|
// ! InitRtti(aAnother.Info, aValue, nil);
|
|
procedure InitFrom(aAnother: PDynArray; var aValue);
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// define the reference to an external count integer variable
|
|
// - Init and InitSpecific methods will reset the aCountPointer to 0: you
|
|
// can use this method to set the external count variable without overriding
|
|
// the current value
|
|
procedure UseExternalCount(aCountPointer: PInteger);
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// initialize the wrapper to point to no dynamic array
|
|
// - it won't clear the wrapped array, just reset the fValue internal pointer
|
|
// - in practice, will disable the other methods
|
|
procedure Void;
|
|
/// check if the wrapper points to a dynamic array
|
|
// - i.e. if Void has been called before
|
|
function IsVoid: boolean;
|
|
/// add an element to the dynamic array
|
|
// - warning: Item must be of the same exact type than the dynamic array,
|
|
// and must be a reference to a variable (you can't write Add(i+10) e.g.)
|
|
// - returns the index of the added element in the dynamic array
|
|
// - note that because of dynamic array internal memory managment, adding
|
|
// may reallocate the list every time a record is added, unless an external
|
|
// count variable has been specified in Init(...,@Count) method
|
|
function Add(const Item): PtrInt;
|
|
/// add an element to the dynamic array, returning its index
|
|
// - note: if you use this method to add a new item with a reference to the
|
|
// dynamic array, be aware that the following trigger a GPF on FPC:
|
|
// ! with Values[DynArray.New] do // otherwise Values is nil -> GPF
|
|
// ! begin
|
|
// ! Field1 := 1;
|
|
// ! ...
|
|
// - so you should either use a local variable:
|
|
// ! i := DynArray.New;
|
|
// ! with Values[i] do // otherwise Values is nil -> GPF
|
|
// ! begin
|
|
// - or even better, don't use the dubious "with Values[...] do" but NewPtr
|
|
function New: PtrInt;
|
|
/// add an element to the dynamic array, returning its pointer
|
|
// - a slightly faster alternative to ItemPtr(New)
|
|
function NewPtr: pointer;
|
|
/// add an element to the dynamic array at the position specified by Index
|
|
// - warning: Item must be of the same exact type than the dynamic array,
|
|
// and must be a reference to a variable (you can't write Insert(10,i+10) e.g.)
|
|
procedure Insert(Index: PtrInt; const Item);
|
|
/// get and remove the last element stored in the dynamic array
|
|
// - Add + Pop/Peek will implement a LIFO (Last-In-First-Out) stack
|
|
// - warning: Dest must be of the same exact type than the dynamic array
|
|
// - returns true if the item was successfully copied and removed
|
|
// - use Peek() if you don't want to remove the item, but just get its value
|
|
function Pop(var Dest): boolean;
|
|
/// get the last element stored in the dynamic array
|
|
// - Add + Pop/Peek will implement a LIFO (Last-In-First-Out) stack
|
|
// - warning: Dest must be of the same exact type than the dynamic array
|
|
// - returns true if the item was successfully copied into Dest
|
|
// - use Pop() if you also want to remove the item
|
|
function Peek(var Dest): boolean;
|
|
/// get and remove the first element stored in the dynamic array
|
|
// - Add + PopHead/PeekHead will implement a FIFO (First-In-First-Out) stack
|
|
// - removing from head will move all items so TSynQueue is faster
|
|
// - warning: Dest must be of the same exact type than the dynamic array
|
|
// - returns true if the item was successfully copied and removed
|
|
// - use PeekHead() if you don't want to remove the item, but get its value
|
|
// - first slot will be deleted and all content moved, so may take some time
|
|
function PopHead(var Dest): boolean;
|
|
/// get the first element stored in the dynamic array
|
|
// - Add + PopHead/PeekHead will implement a FIFO (First-In-First-Out) stack
|
|
// - warning: Dest must be of the same exact type than the dynamic array
|
|
// - returns true if the item was successfully copied and removed
|
|
// - use PopHead() if you also want to remove the item
|
|
function PeekHead(var Dest): boolean;
|
|
/// delete the whole dynamic array content
|
|
// - this method will recognize T*ObjArray types and free all instances
|
|
procedure Clear;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// delete the whole dynamic array content, ignoring exceptions
|
|
// - returns true if no exception occurred when calling Clear, false otherwise
|
|
// - you should better not call this method, which will catch and ignore
|
|
// all exceptions - but it may somewhat make sense in a destructor
|
|
// - this method will recognize T*ObjArray types and free all instances
|
|
function ClearSafe: boolean;
|
|
/// delete one item inside the dynamic array
|
|
// - the deleted element is finalized if necessary
|
|
// - this method will recognize T*ObjArray types and free all instances
|
|
function Delete(aIndex: PtrInt): boolean;
|
|
/// search for an element inside the dynamic array using RTTI
|
|
// - return the index found (0..Count-1), or -1 if Item was not found
|
|
// - will search for all properties content of Item: TList.IndexOf()
|
|
// searches by address, this method searches by content using the RTTI
|
|
// element description (and not the Compare property function)
|
|
// - use the Find() method if you want the search via the Compare property
|
|
// function, or e.g. to search only with some part of the element content
|
|
// - will work with simple types: binaries (byte, word, integer, Int64,
|
|
// Currency, array[0..255] of byte, packed records with no reference-counted
|
|
// type within...), string types (e.g. array of string), and packed records
|
|
// with binary and string types within (like TFileVersion)
|
|
// - won't work with not packed types (like a shorstring, or a record
|
|
// with byte or word fields with {$A+}): in this case, the padding data
|
|
// (i.e. the bytes between the aligned fields) can be filled as random, and
|
|
// there is no way with standard RTTI to identify randomness from values
|
|
// - warning: Item must be of the same exact type than the dynamic array,
|
|
// and must be a reference to a variable (you can't write IndexOf(i+10) e.g.)
|
|
function IndexOf(const Item; CaseInSensitive: boolean = true): PtrInt;
|
|
/// search for an element inside the dynamic array using the Compare function
|
|
// - this method will use the Compare property function, or the supplied
|
|
// aCompare for the search; if none of them are set, it will fallback to
|
|
// IndexOf() to perform a default case-sensitive RTTI search
|
|
// - return the index found (0..Count-1), or -1 if Item was not found
|
|
// - if the array is sorted, it will use fast O(log(n)) binary search
|
|
// - if the array is not sorted, it will use slower O(n) iterating search
|
|
// - warning: Item must be of the same exact type than the dynamic array,
|
|
// and must be a reference to a variable (you can't write Find(i+10) e.g.)
|
|
function Find(const Item; aCompare: TDynArraySortCompare = nil): PtrInt; overload;
|
|
/// search for an element value inside the dynamic array, from an external
|
|
// aIndex[] lookup table - e.g. created by CreateOrderedIndex()
|
|
// - return the index found (0..Count-1), or -1 if Item was not found
|
|
// - if an indexed lookup is supplied, it must already be sorted:
|
|
// this function will then use fast O(log(n)) binary search over aCompare
|
|
// - if the indexed lookup is not correct (e.g. aIndex=nil), iterate O(n)
|
|
// using aCompare - it won't fallback to IndexOf() RTTI search
|
|
// - warning: the lookup aIndex[] should be synchronized if array content
|
|
// is modified (in case of addition or deletion)
|
|
function Find(const Item; const aIndex: TIntegerDynArray;
|
|
aCompare: TDynArraySortCompare): PtrInt; overload;
|
|
/// search for an element value, then fill all properties if match
|
|
// - this method will use the Compare property function for the search,
|
|
// or the supplied indexed lookup table and its associated compare function,
|
|
// and fallback to case-sensitive RTTI search if none is defined
|
|
// - if Item content matches, all Item fields will be filled with the record
|
|
// - can be used e.g. as a simple dictionary: if Compare will match e.g. the
|
|
// first string field (i.e. set to SortDynArrayString), you can fill the
|
|
// first string field with the searched value (if returned index is >= 0)
|
|
// - return the index found (0..Count-1), or -1 if Item was not found
|
|
// - if the array is sorted, it will use fast O(log(n)) binary search
|
|
// - if the array is not sorted, it will use slower O(n) iterating search
|
|
// - warning: Item must be of the same exact type than the dynamic array,
|
|
// and must be a reference to a variable (you can't write Find(i+10) e.g.)
|
|
function FindAndFill(var Item; aIndex: PIntegerDynArray = nil;
|
|
aCompare: TDynArraySortCompare = nil): integer;
|
|
/// search for an element value, then delete it if match
|
|
// - this method will use the Compare property function for the search,
|
|
// or the supplied indexed lookup table and its associated compare function,
|
|
// and fallback to case-sensitive RTTI search if none is defined
|
|
// - if Item content matches, this item will be deleted from the array
|
|
// - can be used e.g. as a simple dictionary: if Compare will match e.g. the
|
|
// first string field (i.e. set to SortDynArrayString), you can fill the
|
|
// first string field with the searched value (if returned index is >= 0)
|
|
// - return the index deleted (0..Count-1), or -1 if Item was not found
|
|
// - if the array is sorted, it will use fast O(log(n)) binary search
|
|
// - if the array is not sorted, it will use slower O(n) iterating search
|
|
// - warning: Item must be of the same exact type than the dynamic array,
|
|
// and must be a reference to a variable (you can't write Find(i+10) e.g.)
|
|
function FindAndDelete(const Item; aIndex: PIntegerDynArray = nil;
|
|
aCompare: TDynArraySortCompare = nil): integer;
|
|
/// search for an element value, then update the item if match
|
|
// - this method will use the Compare property function for the search,
|
|
// or the supplied indexed lookup table and its associated compare function,
|
|
// and fallback to case-sensitive RTTI search if none is defined
|
|
// - if Item content matches, this item will be updated with the supplied value
|
|
// - can be used e.g. as a simple dictionary: if Compare will match e.g. the
|
|
// first string field (i.e. set to SortDynArrayString), you can fill the
|
|
// first string field with the searched value (if returned index is >= 0)
|
|
// - return the index found (0..Count-1), or -1 if Item was not found
|
|
// - if the array is sorted, it will use fast O(log(n)) binary search
|
|
// - if the array is not sorted, it will use slower O(n) iterating search
|
|
// - warning: Item must be of the same exact type than the dynamic array,
|
|
// and must be a reference to a variable (you can't write Find(i+10) e.g.)
|
|
function FindAndUpdate(const Item; aIndex: PIntegerDynArray = nil;
|
|
aCompare: TDynArraySortCompare = nil): integer;
|
|
/// search for an element value, then add it if none matched
|
|
// - this method will use the Compare property function for the search,
|
|
// or the supplied indexed lookup table and its associated compare function,
|
|
// and fallback to case-sensitive RTTI search if none is defined
|
|
// - if no Item content matches, the item will added to the array
|
|
// - can be used e.g. as a simple dictionary: if Compare will match e.g. the
|
|
// first string field (i.e. set to SortDynArrayString), you can fill the
|
|
// first string field with the searched value (if returned index is >= 0)
|
|
// - return the index found (0..Count-1), or -1 if Item was not found and
|
|
// the supplied element has been successfully added
|
|
// - if the array is sorted, it will use fast O(log(n)) binary search
|
|
// - if the array is not sorted, it will use slower O(n) iterating search
|
|
// - warning: Item must be of the same exact type than the dynamic array,
|
|
// and must be a reference to a variable (you can't write Find(i+10) e.g.)
|
|
function FindAndAddIfNotExisting(const Item; aIndex: PIntegerDynArray = nil;
|
|
aCompare: TDynArraySortCompare = nil): integer;
|
|
/// sort the dynamic array items, using the Compare property function
|
|
// - it will change the dynamic array content, and exchange all items
|
|
// in order to be sorted in increasing order according to Compare function
|
|
procedure Sort(aCompare: TDynArraySortCompare = nil); overload;
|
|
/// sort some dynamic array items, using the Compare property function
|
|
// - this method allows to sort only some part of the items
|
|
// - it will change the dynamic array content, and exchange all items
|
|
// in order to be sorted in increasing order according to Compare function
|
|
procedure SortRange(aStart, aStop: integer;
|
|
aCompare: TDynArraySortCompare = nil);
|
|
/// will check all items against aCompare
|
|
function IsSorted(aCompare: TDynArraySortCompare = nil): boolean;
|
|
/// will check all items against aCompare, calling Sort() if needed
|
|
// - faster than plain Sort() if the array is likely to be already sorted
|
|
procedure EnsureSorted(aCompare: TDynArraySortCompare = nil);
|
|
/// sort the dynamic array items, using a Compare method (not function)
|
|
// - it will change the dynamic array content, and exchange all items
|
|
// in order to be sorted in increasing order according to Compare function,
|
|
// unless aReverse is true
|
|
// - it won't mark the array as Sorted, since the comparer is local
|
|
procedure Sort(const aCompare: TOnDynArraySortCompare;
|
|
aReverse: boolean = false); overload;
|
|
/// search the items range which match a given value in a sorted dynamic array
|
|
// - this method will use the Compare property function for the search
|
|
// - returns TRUE and the matching indexes, or FALSE if none found
|
|
// - if the array is not sorted, returns FALSE
|
|
// - warning: FirstIndex/LastIndex parameters should be integer, not PtrInt
|
|
function FindAllSorted(const Item;
|
|
out FirstIndex, LastIndex: integer): boolean; overload;
|
|
/// search the item pointers which match a given value in a sorted dynamic array
|
|
// - this method will use the Compare property function for the search
|
|
// - return nil and FindCount = 0 if no matching item was found
|
|
// - return the a pointer to the first matching item, and FindCount >=1
|
|
// - warning: FindCount out parameter should be integer, not PtrInt
|
|
function FindAllSorted(const Item; out FindCount: integer): pointer; overload;
|
|
/// search for an element value inside a sorted dynamic array
|
|
// - this method will use the Compare property function for the search
|
|
// - will be faster than a manual FindAndAddIfNotExisting+Sort process
|
|
// - returns TRUE and the index of existing Item, or FALSE and the index
|
|
// where the Item is to be inserted so that the array remains sorted
|
|
// - you should then call FastAddSorted() later with the returned Index
|
|
// - if the array is not sorted, returns FALSE and Index=-1
|
|
// - warning: Item must be of the same exact type than the dynamic array,
|
|
// and must be a reference to a variable (no FastLocateSorted(i+10) e.g.)
|
|
// - warning: Index out parameter should be integer, not PtrInt
|
|
function FastLocateSorted(const Item; out Index: integer): boolean;
|
|
/// insert a sorted element value at the proper place
|
|
// - the index should have been computed by FastLocateSorted(): false
|
|
// - you may consider using FastLocateOrAddSorted() instead
|
|
procedure FastAddSorted(Index: PtrInt; const Item);
|
|
/// search and add an element value inside a sorted dynamic array
|
|
// - this method will use the Compare property function for the search
|
|
// - will be faster than a manual FindAndAddIfNotExisting+Sort process
|
|
// - returns the index of the existing Item and wasAdded^=false
|
|
// - returns the sorted index of the inserted Item and wasAdded^=true
|
|
// - if the array is not sorted, returns -1 and wasAdded^=false
|
|
// - is just a wrapper around FastLocateSorted+FastAddSorted
|
|
function FastLocateOrAddSorted(const Item; wasAdded: PBoolean = nil): integer;
|
|
/// delete a sorted element value at the proper place
|
|
// - plain Delete(Index) would reset the fSorted flag to FALSE, so use
|
|
// this method with a FastLocateSorted/FastAddSorted array
|
|
procedure FastDeleteSorted(Index: PtrInt);
|
|
/// will reverse all array items, in place
|
|
procedure Reverse;
|
|
/// will call FillZero() on all items, mainly binaries and strings
|
|
// - could be used on a dynamic array to avoid memory forensic after release
|
|
procedure FillZero;
|
|
/// sort the dynamic array items using a lookup array of indexes
|
|
// - in comparison to the Sort method, this CreateOrderedIndex won't change
|
|
// the dynamic array content, but only create (or update) the supplied
|
|
// integer lookup array, using the specified comparison function
|
|
// - if aCompare is not supplied, the method will use fCompare (if defined)
|
|
// - you should provide either a void either a valid lookup table, that is
|
|
// a table with one to one lookup (e.g. created with FillIncreasing)
|
|
// - if the lookup table has less items than the main dynamic array,
|
|
// its content will be recreated
|
|
procedure CreateOrderedIndex(var aIndex: TIntegerDynArray;
|
|
aCompare: TDynArraySortCompare); overload;
|
|
/// sort the dynamic array items using a lookup array of indexes
|
|
// - this overloaded method will use the supplied TSynTempBuffer for
|
|
// index storage, so use PIntegerArray(aIndex.buf) to access the values
|
|
// - caller should always make aIndex.Done once done
|
|
procedure CreateOrderedIndex(out aIndex: TSynTempBuffer;
|
|
aCompare: TDynArraySortCompare); overload;
|
|
/// sort using a lookup array of indexes, after a Add()
|
|
// - will resize aIndex if necessary, and set aIndex[Count-1] := Count-1
|
|
procedure CreateOrderedIndexAfterAdd(var aIndex: TIntegerDynArray;
|
|
aCompare: TDynArraySortCompare);
|
|
/// save the dynamic array content into a (memory) stream
|
|
// - will handle array of binaries values (byte, word, integer...), array of
|
|
// strings or array of packed records, with binaries and string properties
|
|
// - will use a proprietary binary format, with some variable-length encoding
|
|
// of the string length - note that if you change the type definition, any
|
|
// previously-serialized content will fail, maybe triggering unexpected GPF:
|
|
// use SaveToTypeInfoHash if you share this binary data accross executables
|
|
// - Stream position will be set just after the added data
|
|
// - is optimized for memory streams, but will work with any kind of TStream
|
|
procedure SaveToStream(Stream: TStream);
|
|
/// load the dynamic array content from a (memory) stream
|
|
// - stream content must have been created using SaveToStream method
|
|
// - will handle array of binaries values (byte, word, integer...), array of
|
|
// strings or array of packed records, with binaries and string properties
|
|
// - will use a proprietary binary format, with some variable-length encoding
|
|
// of the string length - note that if you change the type definition, any
|
|
// previously-serialized content will fail, maybe triggering unexpected GPF:
|
|
// use SaveToTypeInfoHash if you share this binary data accross executables
|
|
procedure LoadFromStream(Stream: TCustomMemoryStream);
|
|
/// save the dynamic array content using our binary serialization
|
|
// - will use a proprietary binary format, with some variable-length encoding
|
|
// of the string length - note that if you change the type definition, any
|
|
// previously-serialized content will fail, maybe triggering unexpected GPF
|
|
// - this method will raise an ESynException for T*ObjArray types
|
|
// - use TDynArray.LoadFrom to decode the saved buffer
|
|
// - warning: legacy Hash32 checksum will be stored as 0, so may be refused
|
|
// by mORMot TDynArray.LoadFrom before 1.18.5966
|
|
procedure SaveTo(W: TBufferWriter); overload;
|
|
/// save the dynamic array content into a RawByteString
|
|
// - will use a proprietary binary format, with some variable-length encoding
|
|
// of the string length - note that if you change the type definition, any
|
|
// previously-serialized content will fail, maybe triggering unexpected GPF:
|
|
// use SaveToTypeInfoHash if you share this binary data accross executables
|
|
// - this method will raise an ESynException for T*ObjArray types
|
|
// - use TDynArray.LoadFrom to decode the saved buffer
|
|
// - warning: legacy Hash32 checksum will be stored as 0, so may be refused
|
|
// by mORMot TDynArray.LoadFrom before 1.18.5966
|
|
function SaveTo: RawByteString; overload;
|
|
/// unserialize dynamic array content from binary written by TDynArray.SaveTo
|
|
// - return nil if the Source buffer is incorrect: invalid type, wrong
|
|
// checksum, or SourceMax overflow
|
|
// - return a non nil pointer just after the Source content on success
|
|
// - this method will raise an ESynException for T*ObjArray types
|
|
function LoadFrom(Source: PAnsiChar;
|
|
{$ifdef PUREMORMOT2} // SourceMax is manadatory for safety
|
|
SourceMax: PAnsiChar): PAnsiChar;
|
|
{$else} // mORMot 1 compatibility mode
|
|
SourceMax: PAnsiChar = nil): PAnsiChar;
|
|
{$endif PUREMORMOT2}
|
|
/// unserialize dynamic array content from binary written by TDynArray.SaveTo
|
|
procedure LoadFromReader(var Read: TFastReader);
|
|
/// unserialize the dynamic array content from a TDynArray.SaveTo binary string
|
|
// - same as LoadFrom, and will check for any buffer overflow since we
|
|
// know the actual end of input buffer
|
|
// - will read mORMot 1.18 binary content, but will ignore the Hash32
|
|
// stored checksum which is not needed any more
|
|
function LoadFromBinary(const Buffer: RawByteString): boolean;
|
|
/// serialize the dynamic array content as JSON
|
|
function SaveToJson(EnumSetsAsText: boolean = false;
|
|
reformat: TTextWriterJsonFormat = jsonCompact): RawUtf8; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// serialize the dynamic array content as JSON
|
|
procedure SaveToJson(out result: RawUtf8; EnumSetsAsText: boolean = false;
|
|
reformat: TTextWriterJsonFormat = jsonCompact); overload;
|
|
/// serialize the dynamic array content as JSON
|
|
// - is just a wrapper around TTextWriter.AddTypedJson()
|
|
// - this method will therefore recognize T*ObjArray types
|
|
procedure SaveToJson(out result: RawUtf8; Options: TTextWriterOptions;
|
|
ObjectOptions: TTextWriterWriteObjectOptions = [];
|
|
reformat: TTextWriterJsonFormat = jsonCompact); overload;
|
|
/// serialize the dynamic array content as JSON
|
|
// - is just a wrapper around TTextDateWTTextWriterriter.AddTypedJson()
|
|
// - this method will therefore recognize T*ObjArray types
|
|
procedure SaveToJson(W: TTextWriter;
|
|
ObjectOptions: TTextWriterWriteObjectOptions = []); overload;
|
|
/// load the dynamic array content from an UTF-8 encoded JSON buffer
|
|
// - expect the format as saved by TTextWriter.AddDynArrayJson method, i.e.
|
|
// handling TbooleanDynArray, TIntegerDynArray, TInt64DynArray, TCardinalDynArray,
|
|
// TDoubleDynArray, TCurrencyDynArray, TWordDynArray, TByteDynArray,
|
|
// TRawUtf8DynArray, TWinAnsiDynArray, TRawByteStringDynArray,
|
|
// TStringDynArray, TWideStringDynArray, TSynUnicodeDynArray,
|
|
// TTimeLogDynArray and TDateTimeDynArray as JSON array - or any customized
|
|
// Rtti.RegisterFromText/TRttiJson.RegisterCustomSerializer format
|
|
// - or any other kind of array as Base64 encoded binary stream precessed
|
|
// via JSON_BASE64_MAGIC_C (UTF-8 encoded \uFFF0 special code)
|
|
// - typical handled content could be
|
|
// ! '[1,2,3,4]' or '["\uFFF0base64encodedbinary"]'
|
|
// - return a pointer at the end of the data read from P, nil in case
|
|
// of an invalid input buffer
|
|
// - this method will recognize T*ObjArray types, and will first free
|
|
// any existing instance before unserializing, to avoid memory leak
|
|
// - set e.g. @JSON_[mFast] as CustomVariantOptions parameter to handle
|
|
// complex JSON object or arrays as TDocVariant into variant fields
|
|
// - can use an associated TRawUtf8Interning instance for RawUtf8 values
|
|
// - warning: the content of P^ will be modified during parsing: make a
|
|
// local copy if it will be needed later (using e.g. the overloaded method)
|
|
function LoadFromJson(P: PUtf8Char; EndOfObject: PUtf8Char = nil;
|
|
CustomVariantOptions: PDocVariantOptions = nil; Tolerant: boolean = false;
|
|
Interning: TRawUtf8InterningAbstract = nil): PUtf8Char; overload;
|
|
/// load the dynamic array content from an UTF-8 encoded JSON buffer
|
|
// - this method will make a private copy of the JSON for in-place parsing
|
|
// - returns false in case of invalid input buffer, true on success
|
|
function LoadFromJson(const Json: RawUtf8;
|
|
CustomVariantOptions: PDocVariantOptions = nil; Tolerant: boolean = false;
|
|
Interning: TRawUtf8InterningAbstract = nil): boolean; overload;
|
|
/// select a sub-section (slice) of a dynamic array content
|
|
procedure Slice(var Dest; Limit: cardinal; Offset: cardinal = 0);
|
|
/// assign the current dynamic array content into a variable
|
|
// - by default (Offset=Limit=0), the whole array is set with no memory
|
|
// (re)allocation, just finalize the Dest slot, then make Inc(RefCnt) and
|
|
// force the internal length/Capacity to equal Count
|
|
// - Offset/Limit could be used to create a new dynamic array with some part
|
|
// of the existing content (Offset<0 meaning from the end):
|
|
// ! SliceAsDynArray(DA); // items 0..Count-1 (assign with refcount)
|
|
// ! SliceAsDynArray(DA, 10); // items 10..Count-1
|
|
// ! SliceAsDynArray(DA, 0, 10); // first 0..9 items
|
|
// ! SliceAsDynArray(DA, 10, 20); // items 10..29 - truncated if Count < 20
|
|
// ! SliceAsDynArray(DA, -10); // last Count-10..Count-1 items
|
|
procedure SliceAsDynArray(Dest: PPointer; Offset: integer = 0;
|
|
Limit: integer = 0);
|
|
/// add items from a given dynamic array variable
|
|
// - the supplied source DynArray MUST be of the same exact type as the
|
|
// current used for this TDynArray - warning: pass here a reference to
|
|
// a "array of ..." variable, not another TDynArray instance; if you
|
|
// want to add another TDynArray, use AddDynArray() method
|
|
// - you can specify the start index and the number of items to take from
|
|
// the source dynamic array (leave as -1 to add till the end)
|
|
// - returns the number of items added to the array
|
|
function AddArray(const DynArrayVar; aStartIndex: integer = 0;
|
|
aCount: integer = -1): integer;
|
|
/// add items from a given TDynArray
|
|
// - the supplied source TDynArray MUST be of the same exact type as the
|
|
// current used for this TDynArray, otherwise it won't do anything
|
|
// - you can specify the start index and the number of items to take from
|
|
// the source dynamic array (leave as -1 to add till the end)
|
|
procedure AddDynArray(aSource: PDynArray; aStartIndex: integer = 0;
|
|
aCount: integer = -1);
|
|
/// compare the content of the two arrays, returning TRUE if both match
|
|
// - use any supplied Compare property (unless ignorecompare=true), or
|
|
// following the RTTI element description on all array items
|
|
// - T*ObjArray kind of arrays will properly compare their properties
|
|
function Equals(B: PDynArray; IgnoreCompare: boolean = false;
|
|
CaseSensitive: boolean = true): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// compare the content of the two arrays
|
|
// - use any supplied Compare property (unless ignorecompare=true), or
|
|
// following the RTTI element description on all array items
|
|
// - T*ObjArray kind of arrays will properly compare their properties
|
|
function Compares(B: PDynArray; IgnoreCompare: boolean = false;
|
|
CaseSensitive: boolean = true): integer;
|
|
/// set all content of one dynamic array to the current array
|
|
// - both must be of the same exact type
|
|
// - T*ObjArray will be reallocated and copied by content (using a temporary
|
|
// JSON serialization), unless ObjArrayByRef is true and pointers are copied
|
|
procedure Copy(Source: PDynArray; ObjArrayByRef: boolean = false);
|
|
/// set all content of one dynamic array to the current array
|
|
// - both must be of the same exact type
|
|
// - T*ObjArray will be reallocated and copied by content (using a temporary
|
|
// JSON serialization), unless ObjArrayByRef is true and pointers are copied
|
|
procedure CopyFrom(const Source; MaxItem: integer;
|
|
ObjArrayByRef: boolean = false);
|
|
/// set all content of the current dynamic array to another array variable
|
|
// - both must be of the same exact type
|
|
// - resulting length(Dest) will match the exact items count, even if an
|
|
// external Count integer variable is used by this instance
|
|
// - T*ObjArray will be reallocated and copied by content (using a temporary
|
|
// JSON serialization), unless ObjArrayByRef is true and pointers are copied
|
|
procedure CopyTo(out Dest; ObjArrayByRef: boolean = false);
|
|
/// returns a pointer to an element of the array
|
|
// - returns nil if aIndex is out of range
|
|
// - since TDynArray is just a wrapper around an existing array, you should
|
|
// better use direct access to its wrapped variable, and not this (slightly)
|
|
// slower and more error prone method (such pointer access lacks of strong
|
|
// typing abilities), which is designed for TDynArray abstract/internal use
|
|
function ItemPtr(index: PtrInt): pointer;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// just a convenient wrapper of Info.Cache.ItemSize
|
|
function ItemSize: PtrUInt;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// will copy one element content from its index into another variable
|
|
// - do nothing and return false if index is out of range or Dest is nil
|
|
function ItemCopyAt(index: PtrInt; Dest: pointer): boolean;
|
|
{$ifdef FPC}inline;{$endif}
|
|
/// will move one element content from its index into another variable
|
|
// - will erase the internal item after copy
|
|
// - do nothing and return false if index is out of range or Dest is nil
|
|
function ItemMoveTo(index: PtrInt; Dest: pointer): boolean;
|
|
/// will copy one variable content into an indexed element
|
|
// - do nothing if index is out of range
|
|
// - ClearBeforeCopy will call ItemClear() before the copy, which may be safer
|
|
// if the source item is a copy of Values[index] with some dynamic arrays
|
|
procedure ItemCopyFrom(Source: pointer; index: PtrInt;
|
|
ClearBeforeCopy: boolean = false);
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// compare the content of two items, returning TRUE if both values equal
|
|
// - use the Compare() property function (if set) or using Info.Cache.ItemInfo
|
|
// if available - and fallbacks to binary comparison
|
|
function ItemEquals(A, B: pointer; CaseInSensitive: boolean = false): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// compare the content of two items, returning -1, 0 or +1s
|
|
// - use the Compare() property function (if set) or using Info.Cache.ItemInfo
|
|
// if available - and fallbacks to binary comparison
|
|
function ItemCompare(A, B: pointer; CaseInSensitive: boolean = false): integer;
|
|
/// will reset the element content
|
|
// - i.e. release any managed type memory, and fill Item with zeros
|
|
procedure ItemClear(Item: pointer);
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// will fill the element with some random content
|
|
// - this method is thread-safe using Rtti.DoLock/DoUnLock
|
|
procedure ItemRandom(Item: pointer);
|
|
/// will copy one element content
|
|
procedure ItemCopy(Source, Dest: pointer);
|
|
{$ifdef HASINLINE}{$ifndef ISDELPHI2009}inline;{$endif}{$endif}
|
|
/// will copy the first field value of an array element
|
|
// - will use the array KnownType to guess the copy routine to use
|
|
// - returns false if the type information is not enough for a safe copy
|
|
function ItemCopyFirstField(Source, Dest: Pointer): boolean;
|
|
/// save an array element into a serialized binary content
|
|
// - use the same layout as TDynArray.SaveTo, but for a single item
|
|
// - you can use ItemLoad method later to retrieve its content
|
|
// - warning: Item must be of the same exact type than the dynamic array,
|
|
// and must be a reference to a variable (you can't write ItemSave(i+10) e.g.)
|
|
function ItemSave(Item: pointer): RawByteString;
|
|
/// load an array element as saved by the ItemSave method into Item variable
|
|
// - warning: Item must be of the same exact type than the dynamic array
|
|
procedure ItemLoad(Source, SourceMax: PAnsiChar; Item: pointer);
|
|
/// load an array element as saved by the ItemSave method
|
|
// - this overloaded method will retrieve the element as a memory buffer,
|
|
// which should be cleared by ItemLoadMemClear() before release
|
|
function ItemLoadMem(Source, SourceMax: PAnsiChar): RawByteString;
|
|
/// search for an array element as saved by the ItemSave method
|
|
// - same as ItemLoad() + Find()/IndexOf() + ItemLoadClear()
|
|
// - will call Find() method if Compare property is set
|
|
// - will call generic IndexOf() method if no Compare property is set
|
|
function ItemLoadFind(Source, SourceMax: PAnsiChar): integer;
|
|
/// finalize a temporary buffer used to store an element via ItemLoadMem()
|
|
// - will release any managed type referenced inside the RawByteString,
|
|
// then void the variable
|
|
// - is just a wrapper around ItemClear(pointer(ItemTemp)) + ItemTemp := ''
|
|
procedure ItemLoadMemClear(var ItemTemp: RawByteString);
|
|
|
|
/// retrieve or set the number of items of the dynamic array
|
|
// - same as length(DynArray) or SetLength(DynArray)
|
|
// - this property will recognize T*ObjArray types, so will free any stored
|
|
// instance if the array is sized down
|
|
property Count: PtrInt
|
|
read GetCount write SetCount;
|
|
/// the internal buffer capacity
|
|
// - if no external Count pointer was set with Init, is the same as Count
|
|
// - if an external Count pointer is set, you can set a value to this
|
|
// property before a massive use of the Add() method e.g.
|
|
// - if no external Count pointer is set, set a value to this property
|
|
// will affect the Count value, i.e. Add() will append after this count
|
|
// - this property will recognize T*ObjArray types, so will free any stored
|
|
// instance if the array is sized down
|
|
property Capacity: PtrInt
|
|
read GetCapacity write SetCapacity;
|
|
/// the compare function to be used for Sort and Find methods
|
|
// - by default, no comparison function is set
|
|
// - common functions exist for base types: e.g. SortDynArrayByte, SortDynArrayBoolean,
|
|
// SortDynArrayWord, SortDynArrayInteger, SortDynArrayCardinal, SortDynArraySingle,
|
|
// SortDynArrayInt64, SortDynArrayDouble, SortDynArrayAnsiString,
|
|
// SortDynArrayAnsiStringI, SortDynArrayString, SortDynArrayStringI,
|
|
// SortDynArrayUnicodeString, SortDynArrayUnicodeStringI
|
|
property Compare: TDynArraySortCompare
|
|
read fCompare write SetCompare;
|
|
/// must be TRUE if the array is currently in sorted order according to
|
|
// the compare function
|
|
// - Add/Delete/Insert/Load* methods will reset this property to false
|
|
// - Sort method will set this property to true
|
|
// - you MUST set this property to false if you modify the dynamic array
|
|
// content in your code, so that Find() won't try to wrongly use binary
|
|
// search in an unsorted array, and miss its purpose
|
|
property Sorted: boolean
|
|
read fSorted write fSorted;
|
|
/// can be set to TRUE to avoid any item finalization
|
|
// - e.g. with T*ObjArray - handle with care to avoid memory leaks
|
|
property NoFinalize: boolean
|
|
read fNoFinalize write fNoFinalize;
|
|
|
|
/// low-level direct access to the storage variable
|
|
property Value: PPointer
|
|
read fValue;
|
|
/// low-level extended RTTI access
|
|
// - use e.g. Info.ArrayRtti to access the item RTTI, or Info.Cache.ItemInfo
|
|
// to get the managed item TypeInfo()
|
|
property Info: TRttiCustom
|
|
read fInfo;
|
|
/// low-level direct access to the external count (if defined at Init)
|
|
// - use UseExternalCount() after Init to avoid resetting the count to 0
|
|
property CountExternal: PInteger
|
|
read fCountP;
|
|
end;
|
|
|
|
/// just a wrapper record to join a TDynArray, its Count and a TRWLightLock
|
|
TDynArrayLocked = record
|
|
/// lightweight multiple Reads / exclusive Write non-upgradable lock
|
|
Safe: TRWLightLock;
|
|
/// the wrapper to a dynamic array
|
|
DynArray: TDynArray;
|
|
/// will store the length of the TDynArray
|
|
Count: integer;
|
|
end;
|
|
|
|
|
|
{.$define DYNARRAYHASHCOLLISIONCOUNT} // to be defined also in test.core.base
|
|
|
|
{$ifndef CPU32DELPHI} // Delphi Win32 compiler doesn't like Lemire algorithm
|
|
|
|
{$define DYNARRAYHASH_LEMIRE}
|
|
// use the Lemire 64-bit multiplication for faster hash reduction
|
|
// see https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction
|
|
// - generate more collisions with crc32c, but is always faster -> enabled
|
|
|
|
{$endif CPU32DELPHI}
|
|
|
|
// use Power-Of-Two sizes for smallest HashTables[], to reduce the hash with AND
|
|
// - and Delphi Win32 is not efficient at 64-bit multiplication, anyway
|
|
{$define DYNARRAYHASH_PO2}
|
|
|
|
// use 16-bit Hash table when indexes fit in a word (array Capacity < 65535)
|
|
// - to reduce memory consumption and slightly enhance CPU cache efficiency
|
|
// - e.g. arrays of size 1..127 use only 256*2=512 bytes for their hash table
|
|
{$define DYNARRAYHASH_16BIT}
|
|
|
|
{$ifdef DYNARRAYHASH_PO2}
|
|
const
|
|
/// defined for inlining bitwise division in TDynArrayHasher.HashTableIndex
|
|
// - HashTableSize<=HASH_PO2 is expected to be a power of two (fast binary op);
|
|
// limit is set to 262,144 hash table slots (=1MB), for Capacity=131,072 items
|
|
// - above this limit, a set of increasing primes is used; using a prime as
|
|
// hashtable modulo enhances its distribution, especially for a weak hash function
|
|
// - 64-bit CPU and FPC can efficiently compute a prime reduction using Lemire
|
|
// algorithm, but power of two sizes still have a better practical performance
|
|
// for lower (and most common) content until it consumes too much memory
|
|
HASH_PO2 = 1 shl 18;
|
|
{$endif DYNARRAYHASH_PO2}
|
|
|
|
|
|
|
|
type
|
|
/// function prototype to be used for hashing of a dynamic array element
|
|
// - this function must use the supplied hasher on the Item data
|
|
TDynArrayHashOne = function(const Item; Hasher: THasher): cardinal;
|
|
|
|
/// event handler to be used for hashing of a dynamic array element
|
|
// - can be set as an alternative to TDynArrayHashOne
|
|
TOnDynArrayHashOne = function(const Item): cardinal of object;
|
|
|
|
TDynArrayHasherState = set of (
|
|
hasHasher
|
|
{$ifdef DYNARRAYHASH_16BIT} , hash16bit {$endif} );
|
|
|
|
/// implements O(1) lookup to any dynamic array content
|
|
// - this won't handle the storage process (like add/update), just efficiently
|
|
// maintain a hash table over an existing dynamic array: several TDynArrayHasher
|
|
// could be applied to a single TDynArray wrapper
|
|
// - TDynArrayHashed will use a TDynArrayHasher on its own storage
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TDynArrayHasher = record
|
|
{$else}
|
|
TDynArrayHasher = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
fDynArray: PDynArray;
|
|
fHashItem: TDynArrayHashOne; // function
|
|
fEventHash: TOnDynArrayHashOne; // function of object
|
|
fHashTableStore: TIntegerDynArray; // store 0 for void entry, or Index+1
|
|
fHashTableSize: integer;
|
|
fState: TDynArrayHasherState;
|
|
fCompare: TDynArraySortCompare; // function
|
|
fEventCompare: TOnDynArraySortCompare; // function of object
|
|
fHasher: THasher;
|
|
function HashTableIndex(aHashCode: PtrUInt): PtrUInt;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
function HashTableIndexToIndex(aHashTableIndex: PtrInt): PtrInt;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
procedure HashAdd(aHashCode: cardinal; var result: PtrInt);
|
|
procedure HashDelete(aArrayIndex, aHashTableIndex: PtrInt; aHashCode: cardinal);
|
|
procedure RaiseFatalCollision(const caller: shortstring; aHashCode: cardinal);
|
|
procedure HashTableInit(aHasher: THasher);
|
|
procedure SetEventCompare(const Value: TOnDynArraySortCompare);
|
|
procedure SetEventHash(const Value: TOnDynArrayHashOne);
|
|
public
|
|
{$ifdef DYNARRAYHASHCOLLISIONCOUNT}
|
|
/// low-level access to an hash collisions counter for all instance live
|
|
CountCollisions: cardinal;
|
|
/// low-level access to an hash collisions counter for the last HashTable[]
|
|
CountCollisionsCurrent: cardinal;
|
|
/// low-level access to the size of the internal HashTable[]
|
|
HashTableSize: integer;
|
|
{$endif DYNARRAYHASHCOLLISIONCOUNT}
|
|
/// initialize the hash table for a given dynamic array storage
|
|
// - you can call this method several times, e.g. if aCaseInsensitive changed
|
|
procedure Init(aDynArray: PDynArray; aHashItem: TDynArrayHashOne;
|
|
const aEventHash: TOnDynArrayHashOne; aHasher: THasher; aCompare: TDynArraySortCompare;
|
|
const aEventCompare: TOnDynArraySortCompare; aCaseInsensitive: boolean);
|
|
/// initialize a known hash table for a given dynamic array storage
|
|
// - you can call this method several times, e.g. if aCaseInsensitive changed
|
|
procedure InitSpecific(aDynArray: PDynArray; aKind: TRttiParserType;
|
|
aCaseInsensitive: boolean; aHasher: THasher);
|
|
/// search for an element value inside the dynamic array without hashing
|
|
function Scan(Item: pointer): PtrInt;
|
|
/// search for an element value inside the dynamic array with hashing
|
|
function Find(Item: pointer): PtrInt; overload;
|
|
/// search for a hashed element value inside the dynamic array with hashing
|
|
function Find(Item: pointer; aHashCode: cardinal): PtrInt; overload;
|
|
/// search for a hash position inside the dynamic array with hashing
|
|
function Find(aHashCode: cardinal; aForAdd: boolean): PtrInt; overload;
|
|
/// returns position in array, or next void index in HashTable[] as -(index+1)
|
|
function FindOrNew(aHashCode: cardinal; Item: pointer; aHashTableIndex: PPtrInt): PtrInt;
|
|
/// returns position in array, or -1 if not found with a custom comparer
|
|
function FindOrNewComp(aHashCode: cardinal; Item: pointer;
|
|
Comp: TDynArraySortCompare = nil): PtrInt;
|
|
/// search an hashed element value for adding, updating the internal hash table
|
|
// - trigger hashing if Count reaches CountTrigger
|
|
function FindBeforeAdd(Item: pointer; out wasAdded: boolean;
|
|
aHashCode: cardinal): PtrInt;
|
|
/// search and delete an element value, updating the internal hash table
|
|
function FindBeforeDelete(Item: pointer): PtrInt;
|
|
/// full computation of the internal hash table
|
|
// - to be called after items have been manually updated - e.g. after Clear
|
|
// - can return the number of duplicated values found (likely to be 0)
|
|
procedure ForceReHash(duplicates: PInteger = nil);
|
|
{$ifndef PUREMORMOT2}
|
|
function ReHash(forced: boolean = false): integer;
|
|
{$endif PUREMORMOT2}
|
|
/// compute the hash of a given item
|
|
function HashOne(Item: pointer): cardinal;
|
|
{$ifdef FPC_OR_DELPHIXE4}inline;{$endif}
|
|
{ not inlined to circumvent Delphi 2007=C1632, 2010=C1872, XE3=C2130 }
|
|
/// compare one given item from its index with a value
|
|
// - using either EventCompare() or Compare() functions
|
|
function Equals(Item: pointer; ndx: PtrInt): boolean;
|
|
{$ifdef FPC_OR_DELPHIXE4}inline;{$endif}
|
|
/// retrieve the low-level hash of a given item
|
|
function GetHashFromIndex(aIndex: PtrInt): cardinal;
|
|
/// associated item comparison - may differ from DynArray^.Compare
|
|
property Compare: TDynArraySortCompare
|
|
read fCompare;
|
|
/// custom method-based comparison function
|
|
// - should be set just after Init, when no item has been stored
|
|
property EventCompare: TOnDynArraySortCompare
|
|
read fEventCompare write SetEventCompare;
|
|
/// custom method-based hashing function
|
|
// - should be set just after Init, when no item has been stored
|
|
property EventHash: TOnDynArrayHashOne
|
|
read fEventHash write SetEventHash;
|
|
/// associated item hasher
|
|
property Hasher: THasher
|
|
read fHasher;
|
|
end;
|
|
|
|
/// pointer to a TDynArrayHasher instance
|
|
PDynArrayHasher = ^TDynArrayHasher;
|
|
|
|
type
|
|
/// used to access any dynamic arrray items using fast hash
|
|
// - by default, binary sort could be used for searching items for TDynArray:
|
|
// using a hash is faster on huge arrays for implementing a dictionary
|
|
// - in this current implementation, modification (update or delete) of an
|
|
// element is not handled yet: you should rehash all content - only
|
|
// TDynArrayHashed.FindHashedForAdding / FindHashedAndUpdate /
|
|
// FindHashedAndDelete will refresh the internal hash
|
|
// - this object extends the TDynArray type, since presence of Hashs[] dynamic
|
|
// array will increase code size if using TDynArrayHashed instead of TDynArray
|
|
// - in order to have the better performance, you should use an external Count
|
|
// variable, AND set the Capacity property to the expected maximum count (this
|
|
// will avoid most re-hashing for FindHashedForAdding+FindHashedAndUpdate)
|
|
// - consider using TSynDictionary from mormot.core.json for a thread-safe
|
|
// stand-alone storage of key/value pairs
|
|
{$ifdef UNDIRECTDYNARRAY}
|
|
TDynArrayHashed = record
|
|
// pseudo inheritance for most used methods
|
|
private
|
|
function GetCount: PtrInt; inline;
|
|
procedure SetCount(aCount: PtrInt); inline;
|
|
procedure SetCapacity(aCapacity: PtrInt); inline;
|
|
function GetCapacity: PtrInt; inline;
|
|
public
|
|
InternalDynArray: TDynArray;
|
|
function Value: PPointer; inline;
|
|
function ItemSize: PtrUInt; inline;
|
|
function Info: TRttiCustom; inline;
|
|
procedure Clear; inline;
|
|
procedure ItemCopy(Source, Dest: pointer); inline;
|
|
function ItemPtr(index: PtrInt): pointer; inline;
|
|
function ItemCopyAt(index: PtrInt; Dest: pointer): boolean; inline;
|
|
function Add(const Item): PtrInt; inline;
|
|
procedure Delete(aIndex: PtrInt); inline;
|
|
function SaveTo: RawByteString; overload; inline;
|
|
procedure SaveTo(W: TBufferWriter); overload; inline;
|
|
procedure Sort(aCompare: TDynArraySortCompare = nil); inline;
|
|
function SaveToJson(EnumSetsAsText: boolean = false;
|
|
reformat: TTextWriterJsonFormat = jsonCompact): RawUtf8; overload; inline;
|
|
procedure SaveToJson(out result: RawUtf8; EnumSetsAsText: boolean = false;
|
|
reformat: TTextWriterJsonFormat = jsonCompact); overload; inline;
|
|
procedure SaveToJson(W: TTextWriter); overload; inline;
|
|
function LoadFromJson(P: PUtf8Char; aEndOfObject: PUtf8Char = nil;
|
|
CustomVariantOptions: PDocVariantOptions = nil): PUtf8Char; inline;
|
|
function LoadFrom(Source: PAnsiChar; SourceMax: PAnsiChar
|
|
{$ifndef PUREMORMOT2} = nil{$endif}): PAnsiChar; inline;
|
|
function LoadFromBinary(const Buffer: RawByteString): boolean; inline;
|
|
procedure CreateOrderedIndex(var aIndex: TIntegerDynArray;
|
|
aCompare: TDynArraySortCompare);
|
|
property Count: PtrInt read GetCount write SetCount;
|
|
property Capacity: PtrInt read GetCapacity write SetCapacity;
|
|
private
|
|
{$else UNDIRECTDYNARRAY}
|
|
TDynArrayHashed = object(TDynArray)
|
|
protected
|
|
{$endif UNDIRECTDYNARRAY}
|
|
fHash: TDynArrayHasher;
|
|
function GetHashFromIndex(aIndex: PtrInt): cardinal;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
procedure SetEventCompare(const cmp: TOnDynArraySortCompare);
|
|
procedure SetEventHash(const hsh: TOnDynArrayHashOne);
|
|
public
|
|
/// initialize the wrapper with a one-dimension dynamic array
|
|
// - this version accepts some hash-dedicated parameters: aHashItem to
|
|
// set how to hash each element, aCompare to handle hash collision
|
|
// - if no aHashItem is supplied, it will hash according to the RTTI, i.e.
|
|
// strings or binary types, and the first field for records (strings included)
|
|
// - if no aCompare is supplied, it will use default Equals() method
|
|
// - if no THasher function is supplied, it will use the one supplied in
|
|
// DefaultHasher global variable, set to crc32c() by default - using
|
|
// SSE4.2 instruction if available
|
|
// - if CaseInsensitive is set to TRUE, it will ignore difference in 7-bit
|
|
// alphabetic characters (e.g. compare 'a' and 'A' as equal)
|
|
procedure Init(aTypeInfo: PRttiInfo; var aValue; aHashItem: TDynArrayHashOne = nil;
|
|
aCompare: TDynArraySortCompare = nil; aHasher: THasher = nil;
|
|
aCountPointer: PInteger = nil; aCaseInsensitive: boolean = false);
|
|
/// initialize the wrapper with a one-dimension dynamic array from our RTTI
|
|
procedure InitRtti(aRtti: TRttiCustom; var aValue; aHashItem: TDynArrayHashOne = nil;
|
|
aCompare: TDynArraySortCompare = nil; aHasher: THasher = nil;
|
|
aCountPointer: PInteger = nil; aCaseInsensitive: boolean = false);
|
|
/// initialize the wrapper with a one-dimension dynamic array
|
|
// - this version accepts to specify how both hashing and comparison should
|
|
// occur, setting the TRttiParserType kind of first/hashed field
|
|
// - djNone and djCustom are too vague, and will raise an exception
|
|
// - no RTTI check is made over the corresponding array layout: you shall
|
|
// ensure that aKind matches the dynamic array element definition
|
|
// - aCaseInsensitive will be used for djRawUtf8..djHash512 text comparison
|
|
procedure InitSpecific(aTypeInfo: PRttiInfo; var aValue; aKind: TRttiParserType;
|
|
aCountPointer: PInteger = nil; aCaseInsensitive: boolean = false;
|
|
aHasher: THasher = nil);
|
|
/// will recompute all hash from the current items of the dynamic array
|
|
// - can be called on purpose, when modifications have been performed on
|
|
// the dynamic array content (e.g. in case of element deletion or update,
|
|
// or after calling LoadFrom/Clear method) - this is not necessary after
|
|
// FindHashedForAdding / FindHashedAndUpdate / FindHashedAndDelete methods
|
|
// - returns the number of duplicated items found - which should be 0
|
|
procedure ForceReHash;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
{$ifndef PUREMORMOT2}
|
|
function ReHash(forced: boolean = false): integer;
|
|
{$endif PUREMORMOT2}
|
|
/// search for an element value inside the dynamic array using hashing
|
|
// - Item should be of the type expected by both the hash function and
|
|
// Equals/Compare methods: e.g. if the searched/hashed field in a record is
|
|
// a string as first field, you can safely use a string variable as Item
|
|
// - Item must refer to a variable: e.g. you can't write FindHashed(i+10)
|
|
// - will call fHashItem(Item,fHasher) to compute the needed hash
|
|
// - returns -1 if not found, or the index in the dynamic array if found
|
|
function FindHashed(const Item): PtrInt;
|
|
{$ifdef FPC} inline; {$endif}
|
|
/// search for an element value inside the dynamic array using its hash
|
|
// - returns -1 if not found, or the index in the dynamic array if found
|
|
// - aHashCode parameter constains an already hashed value of the item,
|
|
// to be used e.g. after a call to HashFind()
|
|
function FindFromHash(const Item; aHashCode: cardinal): PtrInt;
|
|
/// search for an element value inside the dynamic array using hashing, and
|
|
// fill ItemToFill with the found content
|
|
// - return the index found (0..Count-1), or -1 if Item was not found
|
|
// - ItemToFill should be of the type expected by the dynamic array, since
|
|
// all its fields will be set on match
|
|
function FindHashedAndFill(var ItemToFill): PtrInt;
|
|
/// search for an element value inside the dynamic array using hashing, and
|
|
// add a void entry to the array if was not found (unless noAddEntry is set)
|
|
// - this method will use hashing for fast retrieval
|
|
// - Item should be of the type expected by both the hash function and
|
|
// Equals/Compare methods: e.g. if the searched/hashed field in a record is
|
|
// a string as first field, you can safely use a string variable as Item
|
|
// - returns either the index in the dynamic array if found (and set wasAdded
|
|
// to false), either the newly created index in the dynamic array (and set
|
|
// wasAdded to true)
|
|
// - for faster process (avoid rehash), please set the Capacity property
|
|
// - warning: in contrast to the Add() method, if an entry is added to the
|
|
// array (wasAdded=true), the entry is left VOID: you must set the field
|
|
// content to expecting value - in short, Item is used only for searching,
|
|
// not copied to the newly created entry in the array - check
|
|
// FindHashedAndUpdate() for a method actually copying Item fields
|
|
function FindHashedForAdding(const Item; out wasAdded: boolean;
|
|
noAddEntry: boolean = false): PtrInt; overload;
|
|
/// search for an element value inside the dynamic array using hashing, and
|
|
// add a void entry to the array if was not found (unless noAddEntry is set)
|
|
// - overloaded method accepting an already hashed value of the item, to be
|
|
// used e.g. after a call to HashFind()
|
|
function FindHashedForAdding(const Item; out wasAdded: boolean;
|
|
aHashCode: cardinal; noAddEntry: boolean = false): PtrInt; overload;
|
|
/// ensure a given element name is unique, then add it to the array
|
|
// - expected element layout is to have a RawUtf8 field at first position
|
|
// - the aName is searched (using hashing) to be unique, and if not the case,
|
|
// an ESynException.CreateUtf8() is raised with the supplied arguments
|
|
// - use internally FindHashedForAdding method
|
|
// - this version will set the field content with the unique value
|
|
// - returns a pointer to the newly added element (to set other fields)
|
|
function AddUniqueName(const aName: RawUtf8; const ExceptionMsg: RawUtf8;
|
|
const ExceptionArgs: array of const;
|
|
aNewIndex: PPtrInt = nil): pointer; overload;
|
|
/// ensure a given element name is unique, then add it to the array
|
|
// - just a wrapper to AddUniqueName(aName,'',[],aNewIndex)
|
|
function AddUniqueName(const aName: RawUtf8;
|
|
aNewIndex: PPtrInt = nil): pointer; overload;
|
|
/// search for a given element name, make it unique, and add it to the array
|
|
// - expected element layout is to have a RawUtf8 field at first position
|
|
// - the aName is searched (using hashing) to be unique, and if not the case,
|
|
// some suffix is added to make it unique, counting from _1 to _999
|
|
// - use internally FindHashedForAdding method
|
|
// - this version will set the field content with the unique value
|
|
// - returns a pointer to the newly added element (to set other fields)
|
|
function AddAndMakeUniqueName(aName: RawUtf8): pointer;
|
|
/// search for an element value inside the dynamic array using hashing, then
|
|
// update any matching item, or add the item if none matched
|
|
// - by design, hashed field shouldn't have been modified by this update,
|
|
// otherwise the method won't be able to find and update the old hash: in
|
|
// this case, you should first call FindHashedAndDelete(OldItem) then
|
|
// FindHashedForAdding(NewItem) to properly handle the internal hash table
|
|
// - if AddIfNotExisting is FALSE, returns the index found (0..Count-1),
|
|
// or -1 if Item was not found - Update will force slow rehash all content
|
|
// - if AddIfNotExisting is TRUE, returns the index found (0..Count-1),
|
|
// or the index newly created/added is the Item value was not matching -
|
|
// add won't rehash all content - for even faster process (avoid rehash),
|
|
// please set the Capacity property
|
|
// - Item should be of the type expected by the dynamic array, since its
|
|
// content will be copied into the dynamic array, and it must refer to a
|
|
// variable: e.g. you can't write FindHashedAndUpdate(i+10)
|
|
function FindHashedAndUpdate(const Item; AddIfNotExisting: boolean): PtrInt;
|
|
/// search for an element value inside the dynamic array using hashing, and
|
|
// delete it if matchs
|
|
// - return the index deleted (0..Count-1), or -1 if Item was not found
|
|
// - can optionally copy the deleted item to FillDeleted^ before erased
|
|
// - Item should be of the type expected by both the hash function and
|
|
// Equals/Compare methods, and must refer to a variable: e.g. you can't
|
|
// write FindHashedAndDelete(i+10)
|
|
// - it won't call slow ForceReHash but refresh the hash table as needed
|
|
function FindHashedAndDelete(const Item; FillDeleted: pointer = nil;
|
|
noDeleteEntry: boolean = false): PtrInt;
|
|
/// search for an element value inside the dynamic array without hashing
|
|
// - is preferred to Find(), since EventCompare would be used if defined
|
|
// - Item should be of the type expected by both the hash function and
|
|
// Equals/Compare methods, and must refer to a variable: e.g. you can't
|
|
// write Scan(i+10)
|
|
// - returns -1 if not found, or the index in the dynamic array if found
|
|
function Scan(const Item): PtrInt;
|
|
/// retrieve the hash value of a given item, from its index
|
|
property Hash[aIndex: PtrInt]: cardinal
|
|
read GetHashFromIndex;
|
|
/// alternative event-oriented Compare function to be used for Sort and Find
|
|
// - will be used instead of Compare, to allow object-oriented callbacks
|
|
// - should be set just after Init, when not item has been stored
|
|
property EventCompare: TOnDynArraySortCompare
|
|
read fHash.fEventCompare write SetEventCompare;
|
|
/// custom hash function used for hashing of a dynamic array element
|
|
property HashItem: TDynArrayHashOne
|
|
read fHash.fHashItem;
|
|
/// alternative event-oriented Hash function
|
|
// - this object-oriented callback will be used instead of HashItem()
|
|
// on each dynamic array entries - HashItem will still be used on
|
|
// const Item values, since they may be just a sub part of the stored entry
|
|
// - should be set just after Init, when not item has been stored
|
|
property EventHash: TOnDynArrayHashOne
|
|
read fHash.fEventHash write SetEventHash;
|
|
/// access to the internal hash table
|
|
// - you can call e.g. Hasher.Clear to invalidate the whole hash table
|
|
property Hasher: TDynArrayHasher
|
|
read fHash;
|
|
end;
|
|
|
|
|
|
/// initialize the structure with a one-dimension dynamic array
|
|
// - the dynamic array must have been defined with its own type
|
|
// (e.g. TIntegerDynArray = array of integer)
|
|
// - if aCountPointer is set, it will be used instead of length() to store
|
|
// the dynamic array items count - it will be much faster when adding
|
|
// elements to the array, because the dynamic array won't need to be
|
|
// resized each time - but in this case, you should use the Count property
|
|
// instead of length(array) or high(array) when accessing the data: in fact
|
|
// length(array) will store the memory size reserved, not the items count
|
|
// - if aCountPointer is set, its content will be set to 0, whatever the
|
|
// array length is, or the current aCountPointer^ value is
|
|
// - a typical usage could be:
|
|
// !var
|
|
// ! IntArray: TIntegerDynArray;
|
|
// !begin
|
|
// ! with DynArray(TypeInfo(TIntegerDynArray), IntArray) do
|
|
// ! begin
|
|
// ! (...)
|
|
// ! end;
|
|
// ! (...)
|
|
// ! bin := DynArray(TypeInfo(TIntegerDynArray), IntArray).SaveTo;
|
|
function DynArray(aTypeInfo: PRttiInfo; var aValue;
|
|
aCountPointer: PInteger = nil): TDynArray;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// get the hash function corresponding to a given standard array type
|
|
// - as used e.g. internally by TDynArrayHasher.Init
|
|
function DynArrayHashOne(Kind: TRttiParserType;
|
|
CaseInsensitive: boolean = false): TDynArrayHashOne;
|
|
|
|
/// sort any dynamic array, generating an external array of indexes
|
|
// - this function will use the supplied TSynTempBuffer for index storage,
|
|
// so use PIntegerArray(Indexes.buf) to access the values
|
|
// - caller should always make Indexes.Done once finshed
|
|
procedure DynArraySortIndexed(Values: pointer; ItemSize, Count: integer;
|
|
out Indexes: TSynTempBuffer; Compare: TDynArraySortCompare); overload;
|
|
|
|
/// sort any dynamic array, via a supplied array of indexes
|
|
// - this function expects Indexes[] to be already allocated and filled
|
|
procedure DynArraySortIndexed(Values: pointer; ItemSize, Count: integer;
|
|
Indexes: PCardinalArray; Compare: TDynArraySortCompare); overload;
|
|
|
|
/// get the comparison function corresponding to a given standard array type
|
|
// - as used e.g. internally by TDynArray
|
|
function DynArraySortOne(Kind: TRttiParserType; CaseInsensitive: boolean): TDynArraySortCompare;
|
|
|
|
/// sort any TObjArray with a given comparison function
|
|
procedure ObjArraySort(var aValue; Compare: TDynArraySortCompare;
|
|
CountPointer: PInteger = nil);
|
|
|
|
|
|
{ *************** Integer Arrays Extended Process }
|
|
|
|
type
|
|
/// event handler called by NotifySortedIntegerChanges()
|
|
// - Sender is an opaque const value, maybe a TObject or any pointer
|
|
TOnNotifySortedIntegerChange = procedure(const Sender; Value: integer) of object;
|
|
|
|
/// compares two 32-bit signed sorted integer arrays, and call event handlers
|
|
// to notify the corresponding modifications in an O(n) time
|
|
// - items in both old[] and new[] arrays are required to be sorted
|
|
procedure NotifySortedIntegerChanges(old, new: PIntegerArray; oldn, newn: PtrInt;
|
|
const added, deleted: TOnNotifySortedIntegerChange; const sender);
|
|
|
|
/// copy an integer array, then sort it, low values first
|
|
procedure CopyAndSortInteger(Values: PIntegerArray; ValuesCount: integer;
|
|
var Dest: TIntegerDynArray);
|
|
|
|
/// copy an integer array, then sort it, low values first
|
|
procedure CopyAndSortInt64(Values: PInt64Array; ValuesCount: integer;
|
|
var Dest: TInt64DynArray);
|
|
|
|
/// remove some 32-bit integer from Values[]
|
|
// - Excluded is declared as var, since it will be sorted in-place during process
|
|
// if it contains more than ExcludedSortSize items (i.e. if the sort is worth it)
|
|
procedure ExcludeInteger(var Values, Excluded: TIntegerDynArray;
|
|
ExcludedSortSize: integer = 32);
|
|
|
|
/// ensure some 32-bit integer from Values[] will only contain Included[]
|
|
// - Included is declared as var, since it will be sorted in-place during process
|
|
// if it contains more than IncludedSortSize items (i.e. if the sort is worth it)
|
|
procedure IncludeInteger(var Values, Included: TIntegerDynArray;
|
|
IncludedSortSize: integer = 32);
|
|
|
|
/// sort and remove any 32-bit duplicated integer from Values[]
|
|
procedure DeduplicateInteger(var Values: TIntegerDynArray); overload;
|
|
|
|
/// sort and remove any 32-bit duplicated integer from Values[]
|
|
// - returns the new Values[] length
|
|
function DeduplicateInteger(var Values: TIntegerDynArray; Count: PtrInt): PtrInt; overload;
|
|
|
|
/// low-level function called by DeduplicateInteger()
|
|
function DeduplicateIntegerSorted(val: PIntegerArray; last: PtrInt): PtrInt;
|
|
|
|
/// create a new 32-bit integer dynamic array with the values from another one
|
|
procedure CopyInteger(const Source: TIntegerDynArray; out Dest: TIntegerDynArray);
|
|
|
|
/// remove some 64-bit integer from Values[]
|
|
// - Excluded is declared as var, since it will be sorted in-place during process
|
|
// if it contains more than ExcludedSortSize items (i.e. if the sort is worth it)
|
|
procedure ExcludeInt64(var Values, Excluded: TInt64DynArray;
|
|
ExcludedSortSize: integer = 32);
|
|
|
|
/// ensure some 64-bit integer from Values[] will only contain Included[]
|
|
// - Included is declared as var, since it will be sorted in-place during process
|
|
// if it contains more than IncludedSortSize items (i.e. if the sort is worth it)
|
|
procedure IncludeInt64(var Values, Included: TInt64DynArray;
|
|
IncludedSortSize: integer = 32);
|
|
|
|
/// sort and remove any 64-bit duplicated integer from Values[]
|
|
procedure DeduplicateInt64(var Values: TInt64DynArray); overload;
|
|
|
|
/// sort and remove any 64-bit duplicated integer from Values[]
|
|
// - returns the new Values[] length
|
|
function DeduplicateInt64(var Values: TInt64DynArray; Count: PtrInt): PtrInt; overload;
|
|
|
|
/// low-level function called by DeduplicateInt64()
|
|
// - warning: caller should ensure that last>0
|
|
function DeduplicateInt64Sorted(val: PInt64Array; last: PtrInt): PtrInt;
|
|
|
|
/// create a new 64-bit integer dynamic array with the values from another one
|
|
procedure CopyInt64(const Source: TInt64DynArray; out Dest: TInt64DynArray);
|
|
|
|
/// find the maximum 32-bit integer in Values[]
|
|
function MaxInteger(const Values: TIntegerDynArray; ValuesCount: PtrInt;
|
|
MaxStart: integer = -1): integer;
|
|
|
|
/// sum all 32-bit integers in Values[]
|
|
function SumInteger(const Values: TIntegerDynArray; ValuesCount: PtrInt): integer;
|
|
|
|
/// fill already allocated Reversed[] so that Reversed[Values[i]]=i
|
|
procedure Reverse(const Values: TIntegerDynArray; ValuesCount: PtrInt;
|
|
Reversed: PIntegerArray);
|
|
|
|
/// copy some Int64 values into an unsigned integer array
|
|
procedure Int64ToUInt32(Values64: PInt64Array; Values32: PCardinalArray; Count: PtrInt);
|
|
|
|
type
|
|
/// comparison function as expected by MedianQuickSelect()
|
|
// - should return TRUE if Values[IndexA]>Values[IndexB]
|
|
TOnValueGreater = function(IndexA, IndexB: PtrInt): boolean of object;
|
|
|
|
/// compute the median of a serie of values, using "Quickselect"
|
|
// - based on the algorithm described in "Numerical recipes in C", Second Edition
|
|
// - expect the values information to be available from a comparison callback
|
|
// - this version will use a temporary index list to exchange items order
|
|
// (supplied as a TSynTempBuffer), so won't change the supplied values themself
|
|
// - see also function MedianQuickSelectInteger() for PIntegerArray values
|
|
// - returns the index of the median Value
|
|
function MedianQuickSelect(const OnCompare: TOnValueGreater; n: integer;
|
|
var TempBuffer: TSynTempBuffer): integer;
|
|
|
|
/// compute the median of an integer serie of values, using "Quickselect"
|
|
// - based on the algorithm described in "Numerical recipes in C", Second Edition,
|
|
// translated from Nicolas Devillard's C code: http://ndevilla.free.fr/median/median
|
|
// - warning: the supplied integer array is modified in-place during the process,
|
|
// and won't be fully sorted on output (this is no QuickSort alternative)
|
|
function MedianQuickSelectInteger(Values: PIntegerArray; n: integer): integer;
|
|
|
|
|
|
/// fast search of a binary value position in a fixed-size array
|
|
// - Count is the number of entries in P^[]
|
|
// - return index of P^[index]=V^, comparing VSize bytes
|
|
// - return -1 if Value was not found
|
|
function AnyScanIndex(P, V: pointer; Count, VSize: PtrInt): PtrInt;
|
|
|
|
/// fast search of a binary value position in a fixed-size array
|
|
// - Count is the number of entries in P^[]
|
|
function AnyScanExists(P, V: pointer; Count, VSize: PtrInt): boolean;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
|
|
|
|
{ ************ INI Files and In-memory Access }
|
|
|
|
/// find a Name= Value in a [Section] of a INI RawUtf8 Content
|
|
// - this function scans the Content memory buffer, and is
|
|
// therefore very fast (no temporary TMemIniFile is created)
|
|
// - if Section equals '', find the Name= value before any [Section]
|
|
function FindIniEntry(const Content, Section, Name: RawUtf8;
|
|
const DefaultValue: RawUtf8 = ''): RawUtf8;
|
|
|
|
/// find a Name= Value in a [Section] of a INI WinAnsi Content
|
|
// - same as FindIniEntry(), but the value is converted from WinAnsi into UTF-8
|
|
function FindWinAnsiIniEntry(const Content, Section, Name: RawUtf8): RawUtf8;
|
|
|
|
/// find a Name= numeric Value in a [Section] of a INI RawUtf8 Content and
|
|
// return it as an integer, or 0 if not found
|
|
// - this function scans the Content memory buffer, and is
|
|
// therefore very fast (no temporary TMemIniFile is created)
|
|
// - if Section equals '', find the Name= value before any [Section]
|
|
function FindIniEntryInteger(const Content, Section, Name: RawUtf8): integer;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// find a Name= Value in a [Section] of a .INI file
|
|
// - if Section equals '', find the Name= value before any [Section]
|
|
// - use internally fast FindIniEntry() function above
|
|
function FindIniEntryFile(const FileName: TFileName;
|
|
const Section, Name: RawUtf8; const DefaultValue: RawUtf8 = ''): RawUtf8;
|
|
|
|
/// update a Name= Value in a [Section] of a INI RawUtf8 Content
|
|
// - this function scans and update the Content memory buffer, and is
|
|
// therefore very fast (no temporary TMemIniFile is created)
|
|
// - if Section equals '', update the Name= value before any [Section]
|
|
procedure UpdateIniEntry(var Content: RawUtf8; const Section, Name, Value: RawUtf8);
|
|
|
|
/// update a Name= Value in a [Section] of a .INI file
|
|
// - if Section equals '', update the Name= value before any [Section]
|
|
// - use internally fast UpdateIniEntry() function above
|
|
procedure UpdateIniEntryFile(const FileName: TFileName; const Section, Name, Value: RawUtf8);
|
|
|
|
/// find the position of the [SEARCH] section in source
|
|
// - return true if [SEARCH] was found, and store pointer to the line after it in source
|
|
function FindSectionFirstLine(var source: PUtf8Char; search: PAnsiChar): boolean;
|
|
|
|
/// find the position of the [SEARCH] section in source
|
|
// - return true if [SEARCH] was found, and store pointer to the line after it in source
|
|
// - this version expects source^ to point to an Unicode char array
|
|
function FindSectionFirstLineW(var source: PWideChar; search: PUtf8Char): boolean;
|
|
|
|
/// retrieve the whole content of a section as a string
|
|
// - SectionFirstLine may have been obtained by FindSectionFirstLine() function above
|
|
function GetSectionContent(SectionFirstLine: PUtf8Char): RawUtf8; overload;
|
|
|
|
/// retrieve the whole content of a section as a string
|
|
// - use SectionFirstLine() then previous GetSectionContent()
|
|
function GetSectionContent(const Content, SectionName: RawUtf8): RawUtf8; overload;
|
|
|
|
/// delete a whole [Section]
|
|
// - if EraseSectionHeader is TRUE (default), then the [Section] line is also
|
|
// deleted together with its content lines
|
|
// - return TRUE if something was changed in Content
|
|
// - return FALSE if [Section] doesn't exist or is already void
|
|
function DeleteSection(var Content: RawUtf8; const SectionName: RawUtf8;
|
|
EraseSectionHeader: boolean = true): boolean; overload;
|
|
|
|
/// delete a whole [Section]
|
|
// - if EraseSectionHeader is TRUE (default), then the [Section] line is also
|
|
// deleted together with its content lines
|
|
// - return TRUE if something was changed in Content
|
|
// - return FALSE if [Section] doesn't exist or is already void
|
|
// - SectionFirstLine may have been obtained by FindSectionFirstLine() function above
|
|
function DeleteSection(SectionFirstLine: PUtf8Char; var Content: RawUtf8;
|
|
EraseSectionHeader: boolean = true): boolean; overload;
|
|
|
|
/// replace a whole [Section] content by a new content
|
|
// - create a new [Section] if none was existing
|
|
procedure ReplaceSection(var Content: RawUtf8; const SectionName,
|
|
NewSectionContent: RawUtf8); overload;
|
|
|
|
/// replace a whole [Section] content by a new content
|
|
// - create a new [Section] if none was existing
|
|
// - SectionFirstLine may have been obtained by FindSectionFirstLine() function above
|
|
procedure ReplaceSection(SectionFirstLine: PUtf8Char;
|
|
var Content: RawUtf8; const NewSectionContent: RawUtf8); overload;
|
|
|
|
/// return TRUE if Value of UpperName does exist in P, till end of current section
|
|
// - expect UpperName as 'NAME='
|
|
function ExistsIniName(P: PUtf8Char; UpperName: PAnsiChar): boolean;
|
|
|
|
/// find the Value of UpperName in P, till end of current section
|
|
// - expect UpperName as 'NAME='
|
|
function FindIniNameValue(P: PUtf8Char; UpperName: PAnsiChar;
|
|
const DefaultValue: RawUtf8 = ''): RawUtf8;
|
|
|
|
/// return TRUE if one of the Value of UpperName exists in P, till end of
|
|
// current section
|
|
// - expect UpperName e.g. as 'CONTENT-TYPE: '
|
|
// - expect UpperValues to be an array of upper values with left side matching,
|
|
// and ending with nil - as expected by IdemPPChar(), i.e. with at least 2 chars
|
|
function ExistsIniNameValue(P: PUtf8Char; const UpperName: RawUtf8;
|
|
UpperValues: PPAnsiChar): boolean;
|
|
|
|
/// find the integer Value of UpperName in P, till end of current section
|
|
// - expect UpperName as 'NAME='
|
|
// - return 0 if no NAME= entry was found
|
|
function FindIniNameValueInteger(P: PUtf8Char; const UpperName: RawUtf8): PtrInt;
|
|
|
|
/// replace a value from a given set of name=value lines
|
|
// - expect UpperName as 'UPPERNAME=', otherwise returns false
|
|
// - if no UPPERNAME= entry was found, then Name+NewValue is added to Content
|
|
// - a typical use may be:
|
|
// ! UpdateIniNameValue(headers,HEADER_CONTENT_TYPE,HEADER_CONTENT_TYPE_UPPER,contenttype);
|
|
function UpdateIniNameValue(var Content: RawUtf8;
|
|
const Name, UpperName, NewValue: RawUtf8): boolean;
|
|
|
|
/// fill a class Instance properties from an .ini content
|
|
// - the class property fields are searched in the supplied main SectionName
|
|
// - nested objects and multi-line text values are searched in their own section,
|
|
// named from their section level and property (e.g. [mainprop.nested1.nested2])
|
|
// - returns true if at least one property has been identified
|
|
function IniToObject(const Ini: RawUtf8; Instance: TObject;
|
|
const SectionName: RawUtf8 = 'Main'; DocVariantOptions: PDocVariantOptions = nil;
|
|
Level: integer = 0): boolean;
|
|
|
|
/// serialize a class Instance properties into an .ini content
|
|
// - the class property fields are written in the supplied main SectionName
|
|
// - nested objects and multi-line text values are written in their own section,
|
|
// named from their section level and property (e.g. [mainprop.nested1.nested2])
|
|
function ObjectToIni(const Instance: TObject; const SectionName: RawUtf8 = 'Main';
|
|
Options: TTextWriterWriteObjectOptions =
|
|
[woEnumSetsAsText, woRawBlobAsBase64, woHumanReadableEnumSetAsComment];
|
|
Level: integer = 0): RawUtf8;
|
|
|
|
/// returns TRUE if the supplied HTML Headers contains 'Content-Type: text/...',
|
|
// 'Content-Type: application/json' or 'Content-Type: application/xml'
|
|
function IsHtmlContentTypeTextual(Headers: PUtf8Char): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// search if the WebSocketUpgrade() header is present
|
|
// - consider checking the hsrConnectionUpgrade flag instead
|
|
function IsWebSocketUpgrade(headers: PUtf8Char): boolean;
|
|
|
|
|
|
{ ************ RawUtf8 String Values Interning and TRawUtf8List }
|
|
|
|
type
|
|
/// store a TRawUtf8DynArray with its efficient hash table
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TRawUtf8Hashed = record
|
|
{$else}
|
|
TRawUtf8Hashed = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
public
|
|
Count: integer;
|
|
Value: TRawUtf8DynArray;
|
|
Values: TDynArrayHashed;
|
|
/// initialize the RawUtf8 dynamic array and hasher
|
|
procedure Init;
|
|
end;
|
|
|
|
/// used to store one list of hashed RawUtf8 in TRawUtf8Interning pool
|
|
// - Delphi "object" is buggy on stack -> also defined as record with methods
|
|
// - each slot has its own TRWLightLock for efficient concurrent reads
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TRawUtf8InterningSlot = record
|
|
{$else}
|
|
TRawUtf8InterningSlot = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
fSafe: TRWLightLock;
|
|
fHash: TRawUtf8Hashed;
|
|
public
|
|
/// initialize the RawUtf8 slot (and its Safe mutex)
|
|
procedure Init;
|
|
/// returns the interned RawUtf8 value
|
|
procedure Unique(var aResult: RawUtf8; const aText: RawUtf8;
|
|
aTextHash: cardinal);
|
|
/// returns the interned RawUtf8 value
|
|
// - only allocates new aResult string if needed
|
|
procedure UniqueFromBuffer(var aResult: RawUtf8;
|
|
aText: PUtf8Char; aTextLen: PtrInt; aTextHash: cardinal);
|
|
/// ensure the supplied RawUtf8 value is interned
|
|
procedure UniqueText(var aText: RawUtf8; aTextHash: cardinal);
|
|
/// return the interned value, if any
|
|
function Existing(const aText: RawUtf8; aTextHash: cardinal): pointer;
|
|
/// delete all stored RawUtf8 values
|
|
procedure Clear;
|
|
/// reclaim any unique RawUtf8 values
|
|
// - any string with an usage count <= aMaxRefCount will be removed
|
|
function Clean(aMaxRefCount: TStrCnt): integer;
|
|
/// how many items are currently stored in Value[]
|
|
property Count: integer
|
|
read fHash.Count;
|
|
end;
|
|
PRawUtf8InterningSlot = ^TRawUtf8InterningSlot;
|
|
|
|
/// allow to store only one copy of distinct RawUtf8 values
|
|
// - thanks to the Copy-On-Write feature of string variables, this may
|
|
// reduce a lot the memory overhead of duplicated text content
|
|
// - this class is thread-safe and optimized for performance
|
|
TRawUtf8Interning = class(TRawUtf8InterningAbstract)
|
|
protected
|
|
fPool: array of TRawUtf8InterningSlot;
|
|
fPoolLast: integer;
|
|
public
|
|
/// initialize the storage and its internal hash pools
|
|
// - aHashTables is the pool size, and should be a power of two <= 512
|
|
// (1, 2, 4, 8, 16, 32, 64, 128, 256, 512)
|
|
constructor Create(aHashTables: integer = 4); reintroduce;
|
|
/// return a RawUtf8 variable stored within this class
|
|
// - if aText occurs for the first time, add it to the internal string pool
|
|
// - if aText does exist in the internal string pool, return the shared
|
|
// instance (with its reference counter increased), to reduce memory usage
|
|
function Unique(const aText: RawUtf8): RawUtf8; overload;
|
|
/// check if a RawUtf8 value is already stored within this class
|
|
// - if not existing, returns nil and don't add it to the pool
|
|
// - if existing, returns pointer(fValue[i]) of the unique stored RawUtf8
|
|
// - use e.g. for very fast per-pointer lookup of interned property names
|
|
function Existing(const aText: RawUtf8): pointer;
|
|
/// return a RawUtf8 variable stored within this class from a text buffer
|
|
// - if aText occurs for the first time, add it to the internal string pool
|
|
// - if aText does exist in the internal string pool, return the shared
|
|
// instance (with its reference counter increased), to reduce memory usage
|
|
function Unique(aText: PUtf8Char; aTextLen: PtrInt): RawUtf8; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// return a RawUtf8 variable stored within this class
|
|
// - if aText occurs for the first time, add it to the internal string pool
|
|
// - if aText does exist in the internal string pool, return the shared
|
|
// instance (with its reference counter increased), to reduce memory usage
|
|
procedure Unique(var aResult: RawUtf8; const aText: RawUtf8); overload;
|
|
/// return a RawUtf8 variable stored within this class from a text buffer
|
|
// - if aText occurs for the first time, add it to the internal string pool
|
|
// - if aText does exist in the internal string pool, return the shared
|
|
// instance (with its reference counter increased), to reduce memory usage
|
|
// - this method won't allocate any memory if aText is already interned
|
|
procedure Unique(var aResult: RawUtf8; aText: PUtf8Char; aTextLen: PtrInt); overload;
|
|
/// ensure a RawUtf8 variable is stored within this class
|
|
// - if aText occurs for the first time, add it to the internal string pool
|
|
// - if aText does exist in the internal string pool, set the shared
|
|
// instance (with its reference counter increased), to reduce memory usage
|
|
procedure UniqueText(var aText: RawUtf8);
|
|
/// return a variant containing a RawUtf8 stored within this class
|
|
// - similar to RawUtf8ToVariant(), but with string interning
|
|
// - see also UniqueVariant() from mormot.core.variants if you want to
|
|
// intern only non-numerical values
|
|
procedure UniqueVariant(var aResult: variant; const aText: RawUtf8); overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// return a variant containing a RawUtf8 stored within this class
|
|
// - similar to RawUtf8ToVariant(StringToUtf8()), but with string interning
|
|
// - this method expects the text to be supplied as a RTL string, which will
|
|
// be converted into a variant containing a RawUtf8 varString instance
|
|
procedure UniqueVariantString(var aResult: variant; const aText: string);
|
|
/// ensure a variant contains only RawUtf8 stored within this class
|
|
// - supplied variant should be a varString containing a RawUtf8 value
|
|
procedure UniqueVariant(var aResult: variant); overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// delete any previous storage pool
|
|
procedure Clear;
|
|
/// reclaim any unique RawUtf8 values
|
|
// - i.e. run a garbage collection process of all values with RefCount=1
|
|
// by default, i.e. all string which are not used any more; you may set
|
|
// aMaxRefCount to a higher value, depending on your expecations, i.e. 2 to
|
|
// delete all string which are referenced only once outside of the pool
|
|
// - returns the number of unique RawUtf8 cleaned from the internal pool
|
|
// - to be executed on a regular basis - but not too often, since the
|
|
// process can be time consumming, and void the benefit of interning
|
|
function Clean(aMaxRefCount: TStrCnt = 1): integer;
|
|
/// how many items are currently stored in this instance
|
|
function Count: integer;
|
|
end;
|
|
|
|
/// possible values used by TRawUtf8List.Flags
|
|
TRawUtf8ListFlags = set of (
|
|
fObjectsOwned,
|
|
fCaseSensitive,
|
|
fNoDuplicate,
|
|
fOnChangeTrigerred,
|
|
fThreadSafe);
|
|
|
|
/// thread-safe TStringList-class optimized for our native UTF-8 string type
|
|
// - can optionally store associated some TObject instances
|
|
// - high-level methods of this class are thread-safe
|
|
// - if fNoDuplicate flag is defined, an internal hash table will be
|
|
// maintained to perform IndexOf() lookups in O(1) linear way
|
|
// - not thread-safe by default, unless fThreadSafe is set to use the TRWLock
|
|
TRawUtf8List = class(TSynPersistentRWLock)
|
|
protected
|
|
fCount: PtrInt;
|
|
fValue: TRawUtf8DynArray;
|
|
fValues: TDynArrayHashed;
|
|
fObjects: TObjectDynArray;
|
|
fFlags: TRawUtf8ListFlags;
|
|
fNameValueSep: AnsiChar;
|
|
fOnChange, fOnChangeBackupForBeginUpdate: TNotifyEvent;
|
|
fOnChangeLevel: integer;
|
|
function GetCount: PtrInt;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
procedure SetCapacity(const capa: PtrInt);
|
|
function GetCapacity: PtrInt;
|
|
function Get(Index: PtrInt): RawUtf8;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
procedure Put(Index: PtrInt; const Value: RawUtf8);
|
|
function GetS(Index: PtrInt): string;
|
|
procedure PutS(Index: PtrInt; const Value: string);
|
|
function GetObject(Index: PtrInt): pointer;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
procedure PutObject(Index: PtrInt; Value: pointer);
|
|
function GetName(Index: PtrInt): RawUtf8;
|
|
function GetValue(const Name: RawUtf8): RawUtf8;
|
|
procedure SetValue(const Name, Value: RawUtf8);
|
|
function GetTextCRLF: RawUtf8;
|
|
procedure SetTextCRLF(const Value: RawUtf8);
|
|
procedure SetTextPtr(P, PEnd: PUtf8Char; const Delimiter: RawUtf8);
|
|
function GetTextPtr: PPUtf8CharArray;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
function GetNoDuplicate: boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
function GetObjectPtr: PPointerArray;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
function GetCaseSensitive: boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
procedure SetCaseSensitive(Value: boolean); virtual;
|
|
procedure Changed; virtual;
|
|
procedure InternalDelete(Index: PtrInt);
|
|
procedure OnChangeHidden(Sender: TObject);
|
|
{$ifndef PUREMORMOT2}
|
|
procedure SetDefaultFlags; virtual;
|
|
{$endif PUREMORMOT2}
|
|
public
|
|
/// initialize the RawUtf8/Objects storage with [fCaseSensitive] flags
|
|
constructor Create; overload; override;
|
|
/// initialize the RawUtf8/Objects storage with extended flags
|
|
// - by default, any associated Objects[] are just weak references;
|
|
// you may supply fOwnObjects flag to force object instance management
|
|
// - if you want the stored text items to be unique, set fNoDuplicate
|
|
// and then an internal hash table will be maintained for fast IndexOf()
|
|
// - you can set fCaseSensitive to let the UTF-8 lookup be case-sensitive
|
|
// - not thread-safe by default, unless fThreadSafe is set to use a R/W lock
|
|
// - is defined as CreateEx instead of overload Create to avoid weird Delphi
|
|
// compilation issues, especially within packages
|
|
constructor CreateEx(aFlags: TRawUtf8ListFlags);
|
|
{$ifndef PUREMORMOT2}
|
|
/// backward compatiliby overloaded constructor
|
|
// - please rather use the overloaded CreateEx(TRawUtf8ListFlags)
|
|
// - for instance, Create(true) is CreateEx([fObjectsOwned, fCaseSensitive]);
|
|
constructor Create(aOwnObjects: boolean; aNoDuplicate: boolean = false;
|
|
aCaseSensitive: boolean = true); reintroduce; overload;
|
|
{$endif PUREMORMOT2}
|
|
/// finalize the internal objects stored
|
|
// - if instance was created with fOwnObjects flag
|
|
destructor Destroy; override;
|
|
/// get a stored Object item by its associated UTF-8 text
|
|
// - returns nil and raise no exception if aText doesn't exist
|
|
// - thread-safe method, unless returned TObject is deleted in the background
|
|
function GetObjectFrom(const aText: RawUtf8): pointer;
|
|
/// store a new RawUtf8 item
|
|
// - without the fNoDuplicate flag, it will always add the supplied value
|
|
// - if fNoDuplicate was set and aText already exists (using the internal
|
|
// hash table), it will return -1 unless aRaiseExceptionIfExisting is forced
|
|
// - thread-safe method
|
|
function Add(const aText: RawUtf8;
|
|
aRaiseExceptionIfExisting: boolean = false): PtrInt;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// store a new RawUtf8 item, and its associated TObject
|
|
// - without the fNoDuplicate flag, it will always add the supplied value
|
|
// - if fNoDuplicate was set and aText already exists (using the internal hash
|
|
// table), it will return -1 unless aRaiseExceptionIfExisting is forced;
|
|
// optionally freeing the supplied aObject if aFreeAndReturnExistingObject
|
|
// is set, in which pointer the existing Objects[] is copied (see
|
|
// AddObjectUnique as a convenient wrapper around this behavior);
|
|
// if aFreeAndReturnExistingObject is nil, and aReplaceExistingObject is
|
|
// true, the existing object is freed and replaced by aObject
|
|
// - thread-safe method
|
|
function AddObject(const aText: RawUtf8; aObject: TObject;
|
|
aRaiseExceptionIfExisting: boolean = false;
|
|
aFreeAndReturnExistingObject: PPointer = nil;
|
|
aReplaceExistingObject: boolean = false): PtrInt;
|
|
/// try to store a new RawUtf8 item and its associated TObject
|
|
// - fNoDuplicate should have been specified in the list flags
|
|
// - if aText doesn't exist, will add the values
|
|
// - if aText exist, will call aObjectToAddOrFree.Free and set the value
|
|
// already stored in Objects[] into aObjectToAddOrFree - allowing dual
|
|
// commit thread-safe update of the list, e.g. after a previous unsuccessful
|
|
// call to GetObjectFrom(aText)
|
|
// - thread-safe method, using an internal Hash Table to speedup IndexOf()
|
|
// - in fact, this method is just a wrapper around
|
|
// ! AddObject(aText,aObjectToAddOrFree^,false,@aObjectToAddOrFree);
|
|
procedure AddObjectUnique(const aText: RawUtf8; aObjectToAddOrFree: PPointer);
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// force the storage of a RawUtf8 item, and its associated TObject
|
|
// - without the fNoDuplicate flag, it will always add the supplied value
|
|
// - if fNoDuplicate was set and aText already exists (using the internal hash
|
|
// table), it will free any existing Objects[] and put aObject in its place
|
|
// - thread-safe method, using an internal Hash Table to speedup IndexOf()
|
|
function AddOrReplaceObject(const aText: RawUtf8; aObject: TObject): PtrInt;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// append a specified list to the current content
|
|
// - thread-safe method
|
|
procedure AddRawUtf8List(List: TRawUtf8List);
|
|
/// delete a stored RawUtf8 item, and its associated TObject
|
|
// - raise no exception in case of out of range supplied index
|
|
// - this method is not thread-safe: use Safe.Lock/UnLock if needed
|
|
procedure Delete(Index: PtrInt); overload;
|
|
/// delete a stored RawUtf8 item, and its associated TObject
|
|
// - will search for the value using IndexOf(aText), and returns its index
|
|
// - returns -1 if no entry was found and deleted
|
|
// - thread-safe method, using the internal Hash Table if fNoDuplicate is set
|
|
function Delete(const aText: RawUtf8): PtrInt; overload;
|
|
/// delete a stored RawUtf8 item, and its associated TObject, from
|
|
// a given Name when stored as 'Name=Value' pairs
|
|
// - raise no exception in case of out of range supplied index
|
|
// - thread-safe method, but not using the internal Hash Table
|
|
// - consider using TSynNameValue if you expect efficient name/value process
|
|
function DeleteFromName(const Name: RawUtf8): PtrInt; virtual;
|
|
/// find the index of a given Name when stored as 'Name=Value' pairs
|
|
// - search on Name is case-insensitive with 'Name=Value' pairs
|
|
// - this method is not thread-safe, and won't use the internal Hash Table
|
|
// - consider using TSynNameValue if you expect efficient name/value process
|
|
function IndexOfName(const Name: RawUtf8): PtrInt;
|
|
/// access to the Value of a given 'Name=Value' pair at a given position
|
|
// - this method is not thread-safe
|
|
// - consider using TSynNameValue if you expect efficient name/value process
|
|
function GetValueAt(Index: PtrInt): RawUtf8;
|
|
/// compare a Value with some RawUtf8 text
|
|
// - this method is not thread-safe
|
|
function EqualValueAt(Index: PtrInt; const aText: RawUtf8): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// retrieve Value from an existing Name=Value, then optinally delete the entry
|
|
// - if Name is found, will fill Value with the stored content and return true
|
|
// - if Name is not found, Value is not modified, and false is returned
|
|
// - thread-safe method, but not using the internal Hash Table
|
|
// - consider using TSynNameValue if you expect efficient name/value process
|
|
function UpdateValue(const Name: RawUtf8; var Value: RawUtf8;
|
|
ThenDelete: boolean): boolean;
|
|
/// retrieve and delete the first RawUtf8 item in the list
|
|
// - could be used as a FIFO, calling Add() as a "push" method
|
|
// - thread-safe method
|
|
function PopFirst(out aText: RawUtf8; aObject: PObject = nil): boolean;
|
|
/// retrieve and delete the last RawUtf8 item in the list
|
|
// - could be used as a FILO, calling Add() as a "push" method
|
|
// - thread-safe method
|
|
function PopLast(out aText: RawUtf8; aObject: PObject = nil): boolean;
|
|
/// erase all stored RawUtf8 items
|
|
// - and corresponding objects (if aOwnObjects was true at constructor)
|
|
// - thread-safe method, also clearing the internal Hash Table
|
|
procedure Clear; virtual;
|
|
/// find a RawUtf8 item in the stored Strings[] list
|
|
// - this search is case sensitive if fCaseSensitive flag was set (which
|
|
// is the default)
|
|
// - this method is not thread-safe since the internal list may change
|
|
// and the returned index may not be accurate any more
|
|
// - see also Exists() and GetObjectFrom() method
|
|
// - uses the internal Hash Table if fNoDuplicate was set
|
|
function IndexOf(const aText: RawUtf8): PtrInt;
|
|
/// find a RawUtf8 item in the stored Strings[] list
|
|
// - search is case sensitive if fCaseSensitive flag was set (default)
|
|
// - this method is thread-safe
|
|
// - uses the internal Hash Table if fNoDuplicate was set
|
|
function Exists(const aText: RawUtf8): boolean;
|
|
/// find a TObject item index in the stored Objects[] list
|
|
// - this method is not thread-safe since the internal list may change
|
|
// and the returned index may not be accurate any more
|
|
// - aObject lookup won't use the internal Hash Table
|
|
function IndexOfObject(aObject: TObject): PtrInt;
|
|
/// search for any RawUtf8 item containing some text
|
|
// - uses PosEx() on the stored lines
|
|
// - this method is not thread-safe since the internal list may change
|
|
// and the returned index may not be accurate any more
|
|
// - by design, aText lookup can't use the internal Hash Table
|
|
function Contains(const aText: RawUtf8; aFirstIndex: integer = 0): PtrInt;
|
|
/// retrieve the all lines, separated by the supplied delimiter
|
|
// - this method is thread-safe
|
|
function GetText(const Delimiter: RawUtf8 = #13#10): RawUtf8;
|
|
/// the OnChange event will be raised only when EndUpdate will be called
|
|
// - this method will also call Safe.Lock for thread-safety
|
|
procedure BeginUpdate;
|
|
/// call the OnChange event if changes occurred
|
|
// - this method will also call Safe.UnLock for thread-safety
|
|
procedure EndUpdate;
|
|
/// set low-level text and objects from existing arrays
|
|
procedure SetFrom(const aText: TRawUtf8DynArray; const aObject: TObjectDynArray);
|
|
/// set all lines, separated by the supplied delimiter
|
|
// - this method is thread-safe
|
|
procedure SetText(const aText: RawUtf8; const Delimiter: RawUtf8 = #13#10);
|
|
/// set all lines from a text file
|
|
// - will assume text file with no BOM is already UTF-8 encoded
|
|
// - this method is thread-safe
|
|
procedure LoadFromFile(const FileName: TFileName);
|
|
/// write all lines into the supplied stream
|
|
// - this method is thread-safe
|
|
procedure SaveToStream(Dest: TStream; const Delimiter: RawUtf8 = #13#10);
|
|
/// write all lines into a new UTF-8 file
|
|
// - this method is thread-safe
|
|
procedure SaveToFile(const FileName: TFileName; const Delimiter: RawUtf8 = #13#10);
|
|
/// return the count of stored RawUtf8
|
|
// - reading this property is not thread-safe, since size may change
|
|
property Count: PtrInt
|
|
read GetCount;
|
|
/// set or retrieve the current memory capacity of the RawUtf8 list
|
|
// - reading this property is not thread-safe, since size may change
|
|
property Capacity: PtrInt
|
|
read GetCapacity write SetCapacity;
|
|
/// set if IndexOf() shall be case sensitive or not
|
|
// - default is TRUE
|
|
// - matches fCaseSensitive in Flags
|
|
property CaseSensitive: boolean
|
|
read GetCaseSensitive write SetCaseSensitive;
|
|
/// set if the list doesn't allow duplicated UTF-8 text
|
|
// - if true, an internal hash table is maintained for faster IndexOf()
|
|
// - matches fNoDuplicate in Flags
|
|
property NoDuplicate: boolean
|
|
read GetNoDuplicate;
|
|
/// access to the low-level flags of this list
|
|
property Flags: TRawUtf8ListFlags
|
|
read fFlags write fFlags;
|
|
/// get or set a RawUtf8 item
|
|
// - returns '' and raise no exception in case of out of range supplied index
|
|
// - if you want to use it with the UI, use Utf8ToString() function
|
|
// - reading this property is not thread-safe, since content may change
|
|
property Strings[Index: PtrInt]: RawUtf8
|
|
read Get write Put; default;
|
|
/// get or set an item as RTL string, ready to be used with the UI
|
|
// - returns '' and raise no exception in case of out of range supplied index
|
|
// - wrap Strings[] with Utf8ToString/StringToUtf8 functions
|
|
// - reading this property is not thread-safe, since content may change
|
|
property Str[Index: PtrInt]: string
|
|
read GetS write PutS;
|
|
/// get or set a Object item
|
|
// - returns nil and raise no exception in case of out of range supplied index
|
|
// - reading this property is not thread-safe, since content may change
|
|
property Objects[Index: PtrInt]: pointer
|
|
read GetObject write PutObject;
|
|
/// retrieve the corresponding Name when stored as 'Name=Value' pairs
|
|
// - reading this property is not thread-safe, since content may change
|
|
// - consider TSynNameValue if you expect more efficient name/value process
|
|
property Names[Index: PtrInt]: RawUtf8
|
|
read GetName;
|
|
/// access to the corresponding 'Name=Value' pairs
|
|
// - search on Name is case-insensitive with 'Name=Value' pairs
|
|
// - reading this property is thread-safe, but won't use the hash table
|
|
// - consider TSynNameValue if you expect more efficient name/value process
|
|
property Values[const Name: RawUtf8]: RawUtf8
|
|
read GetValue write SetValue;
|
|
/// the char separator between 'Name=Value' pairs
|
|
// - equals '=' by default
|
|
// - consider TSynNameValue if you expect more efficient name/value process
|
|
property NameValueSep: AnsiChar
|
|
read fNameValueSep write fNameValueSep;
|
|
/// set or retrieve all items as text lines
|
|
// - lines are separated by #13#10 (CRLF) by default; use GetText and
|
|
// SetText methods if you want to use another line delimiter (even a comma)
|
|
// - this property is thread-safe
|
|
property Text: RawUtf8
|
|
read GetTextCRLF write SetTextCRLF;
|
|
/// Event triggered when an entry is modified
|
|
property OnChange: TNotifyEvent
|
|
read fOnChange write fOnChange;
|
|
/// direct access to the memory of the TRawUtf8DynArray items
|
|
// - reading this property is not thread-safe, since content may change
|
|
property TextPtr: PPUtf8CharArray
|
|
read GetTextPtr;
|
|
/// direct access to the memory of the TObjectDynArray items
|
|
// - reading this property is not thread-safe, since content may change
|
|
property ObjectPtr: PPointerArray
|
|
read GetObjectPtr;
|
|
/// direct access to the TRawUtf8DynArray instance
|
|
// - reading this property is not thread-safe, since content may change
|
|
property ValuePtr: TRawUtf8DynArray
|
|
read fValue;
|
|
/// direct access to the TRawUtf8DynArray items dynamic array wrapper
|
|
// - using this property is not thread-safe, since content may change
|
|
property ValuesArray: TDynArrayHashed
|
|
read fValues;
|
|
end;
|
|
|
|
PRawUtf8List = ^TRawUtf8List;
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
|
|
// some declarations used for backward compatibility only
|
|
TRawUtf8ListLocked = class(TRawUtf8List)
|
|
protected procedure SetDefaultFlags; override; end;
|
|
TRawUtf8ListHashed = class(TRawUtf8List)
|
|
protected procedure SetDefaultFlags; override; end;
|
|
TRawUtf8ListHashedLocked = class(TRawUtf8ListHashed)
|
|
protected procedure SetDefaultFlags; override; end;
|
|
// deprecated TRawUtf8MethodList should be replaced by a TSynDictionary
|
|
|
|
{$endif PUREMORMOT2}
|
|
|
|
/// sort a dynamic array of PUtf8Char items, via an external array of indexes
|
|
// - you can use FastFindIndexedPUtf8Char() for fast O(log(n)) binary search
|
|
procedure QuickSortIndexedPUtf8Char(Values: PPUtf8CharArray; Count: integer;
|
|
var SortedIndexes: TCardinalDynArray; CaseSensitive: boolean = false);
|
|
|
|
var
|
|
/// low-level JSON unserialization function
|
|
// - defined in this unit to avoid circular reference with mormot.core.json,
|
|
// but to publish the TDynArray.LoadFromJson overloaded methods
|
|
// - this unit will just set a wrapper raising an ERttiException
|
|
// - link mormot.core.json.pas to have a working implementation
|
|
// - rather call LoadJson() from mormot.core.json than this low-level function
|
|
GetDataFromJson: procedure(Data: pointer; var Json: PUtf8Char;
|
|
EndOfObject: PUtf8Char; Rtti: TRttiCustom;
|
|
CustomVariantOptions: PDocVariantOptions; Tolerant: boolean;
|
|
Interning: TRawUtf8InterningAbstract);
|
|
|
|
|
|
{ ************ Abstract Radix Tree Classes }
|
|
|
|
type
|
|
TRadixTree = class;
|
|
|
|
/// refine the TRadixTreeNode content
|
|
// - rtfParam is <param> node, i.e. a TRadixTreeNodeParams with Names <> nil
|
|
// - rtfParamInteger is for a rtfParam which value should be only an integer,
|
|
// either from rtoIntegerParams global flag, or individually as <int:###>
|
|
// - rtfParamPath is for a rtfParam which value should be the whole path,
|
|
// until the end of the URI or the beginning of the parameters (i.e. at '?'),
|
|
// set individually as <path:###> parameter - * being synonymous to <path:path>
|
|
TRadixTreeNodeFlags = set of (
|
|
rtfParam,
|
|
rtfParamInteger,
|
|
rtfParamPath);
|
|
|
|
/// implement an abstract Radix Tree node
|
|
TRadixTreeNode = class
|
|
protected
|
|
function ComputeDepth: integer;
|
|
procedure SortChildren;
|
|
public
|
|
/// the main Tree holding this node
|
|
Owner: TRadixTree;
|
|
/// the characters to be compared at this level
|
|
Chars: RawUtf8;
|
|
/// how many branches are within this node - used to sort by priority
|
|
Depth: integer;
|
|
/// describe the content of this node
|
|
Flags: TRadixTreeNodeFlags;
|
|
/// the nested nodes
|
|
Child: array of TRadixTreeNode;
|
|
/// the whole text up to this level
|
|
FullText: RawUtf8;
|
|
/// initialize this node instance
|
|
constructor Create(aOwner: TRadixTree); reintroduce;
|
|
/// instantiate a new node with the same class and properties
|
|
function Split(const Text: RawUtf8): TRadixTreeNode; virtual;
|
|
/// finalize this Radix Tree node
|
|
destructor Destroy; override;
|
|
/// search for the node corresponding to a given text
|
|
function Find(P: PUtf8Char): TRadixTreeNode;
|
|
/// internal debugging/testing method
|
|
procedure ToText(var Result: RawUtf8; Level: integer);
|
|
end;
|
|
|
|
/// our TRadixTree works on dynamic/custom types of node classes
|
|
TRadixTreeNodeClass = class of TRadixTreeNode;
|
|
|
|
/// allow to customize TRadixTree process
|
|
// - e.g. if static text matching should be case-insensitive (but <params> are
|
|
// always case-sensitive, because they are user-specific runtime variables)
|
|
// - if <param> values should be only plain integers, never alphabetical text -
|
|
// you may also specify int:xxx for a single parameter, e.g. as <int:id>
|
|
TRadixTreeOptions = set of (
|
|
rtoCaseInsensitiveUri,
|
|
rtoIntegerParams);
|
|
|
|
/// implement an abstract Radix Tree over UTF-8 case-insensitive text
|
|
// - as such, this class is not very useful if you just need to lookup for
|
|
// a text value: a TDynArrayHasher/TDictionary is faster and uses less RAM
|
|
// - but, once extended e.g. as TUriTree, it can very efficiently parse
|
|
// some text with variants parts (e.g. parameters)
|
|
TRadixTree = class
|
|
protected
|
|
fRoot: TRadixTreeNode;
|
|
fDefaultNodeClass: TRadixTreeNodeClass;
|
|
fOptions: TRadixTreeOptions;
|
|
fNormTable: PNormTable; // for efficient rtoCaseInsensitiveUri
|
|
public
|
|
/// initialize the Radix Tree
|
|
constructor Create(aNodeClass: TRadixTreeNodeClass;
|
|
aOptions: TRadixTreeOptions = []); reintroduce;
|
|
/// finalize this Radix Tree
|
|
destructor Destroy; override;
|
|
/// define how TRadixTreeNode.Lookup() will process this node
|
|
// - as set with this class constructor
|
|
property Options: TRadixTreeOptions
|
|
read fOptions;
|
|
/// finalize this Radix Tree node
|
|
procedure Clear;
|
|
/// low-level insertion of a given Text entry as a given child
|
|
// - may return an existing node instance, if Text was already inserted
|
|
function Insert(Text: RawUtf8; Node: TRadixTreeNode = nil;
|
|
NodeClass: TRadixTreeNodeClass = nil): TRadixTreeNode;
|
|
/// to be called after Insert() to consolidate the internal tree state
|
|
// - nodes will be sorted by search priority, i.e. the longest depths first
|
|
// - as called e.g. by TUriTree.Setup()
|
|
procedure AfterInsert;
|
|
/// search for the node corresponding to a given text
|
|
// - more than 6 million lookups per second, with 1000 items stored
|
|
function Find(const Text: RawUtf8): TRadixTreeNode;
|
|
/// internal debugging/testing method
|
|
function ToText: RawUtf8;
|
|
/// low-level access to the root node of the Radix Tree
|
|
property Root: TRadixTreeNode
|
|
read fRoot;
|
|
end;
|
|
|
|
/// implement an abstract Radix Tree static or <param> node
|
|
TRadixTreeNodeParams = class(TRadixTreeNode)
|
|
protected
|
|
/// is called for each <param> as Pos/Len pair
|
|
// - called eventually with Pos^='?' and Len=-1 for the inlined parameters
|
|
// - should return true on success, false to abort
|
|
function LookupParam(Ctxt: TObject; Pos: PUtf8Char; Len: integer): boolean;
|
|
virtual; abstract;
|
|
public
|
|
/// all the <param1> <param2> names, in order, up to this parameter
|
|
// - equals nil for static nodes
|
|
// - is referenced as pointer into THttpServerRequestAbstract.fRouteName
|
|
Names: TRawUtf8DynArray;
|
|
/// overriden to support the additional Names fields
|
|
function Split(const Text: RawUtf8): TRadixTreeNode; override;
|
|
/// main search method, recognizing static or <param> patterns
|
|
function Lookup(P: PUtf8Char; Ctxt: TObject): TRadixTreeNodeParams;
|
|
end;
|
|
|
|
/// implement an abstract Radix Tree with static or <param> nodes
|
|
TRadixTreeParams = class(TRadixTree)
|
|
public
|
|
/// low-level registration of a new URI path, with <param> support
|
|
// - returns the node matching the given URI
|
|
// - called e.g. from TUriRouter.Rewrite/Run methods
|
|
// - will recognize <param> alphanumerical and <int:id> integer parameters
|
|
function Setup(const aFromUri: RawUtf8; out aNames: TRawUtf8DynArray): TRadixTreeNodeParams;
|
|
end;
|
|
|
|
ERadixTree = class(ESynException);
|
|
|
|
|
|
implementation
|
|
|
|
|
|
{ ************ RTL TPersistent / TInterfacedObject with Custom Constructor }
|
|
|
|
{ TPersistentWithCustomCreate }
|
|
|
|
constructor TPersistentWithCustomCreate.Create;
|
|
begin
|
|
// nothing to do by default - overridden constructor may add custom code
|
|
end;
|
|
|
|
|
|
{ TInterfacedObjectWithCustomCreate }
|
|
|
|
constructor TInterfacedObjectWithCustomCreate.Create;
|
|
begin
|
|
// nothing to do by default - overridden constructor may add custom code
|
|
end;
|
|
|
|
procedure TInterfacedObjectWithCustomCreate.RefCountUpdate(Release: boolean);
|
|
begin
|
|
if Release then
|
|
_Release
|
|
else
|
|
_AddRef;
|
|
end;
|
|
|
|
|
|
{ TInterfacedCollection }
|
|
|
|
constructor TInterfacedCollection.Create;
|
|
begin
|
|
inherited Create(GetClass);
|
|
end;
|
|
|
|
|
|
{ TSynInterfacedObject }
|
|
|
|
constructor TSynInterfacedObject.Create;
|
|
begin
|
|
// do-nothing virtual constructor
|
|
end;
|
|
|
|
function TSynInterfacedObject._AddRef: TIntCnt;
|
|
begin
|
|
result := VirtualAddRef;
|
|
end;
|
|
|
|
function TSynInterfacedObject._Release: TIntCnt;
|
|
begin
|
|
result := VirtualRelease;
|
|
end;
|
|
|
|
function TSynInterfacedObject.QueryInterface(
|
|
{$ifdef FPC_HAS_CONSTREF}constref{$else}const{$endif} IID: TGuid;
|
|
out Obj): TIntQry;
|
|
begin
|
|
result := VirtualQueryInterface(@IID, Obj);
|
|
end;
|
|
|
|
function TSynInterfacedObject.VirtualQueryInterface(IID: PGuid; out Obj): TIntQry;
|
|
begin
|
|
result := E_NOINTERFACE;
|
|
end;
|
|
|
|
|
|
{ TAutoFree }
|
|
|
|
constructor TAutoFree.Create(var localVariable; obj: TObject);
|
|
begin
|
|
fObject := obj;
|
|
TObject(localVariable) := obj;
|
|
end;
|
|
|
|
constructor TAutoFree.Create(const varObjPairs: array of pointer);
|
|
var
|
|
n, i: PtrInt;
|
|
begin
|
|
n := length(varObjPairs);
|
|
if (n = 0) or
|
|
(n and 1 = 1) then
|
|
exit;
|
|
n := n shr 1;
|
|
if n = 0 then
|
|
exit;
|
|
if n = 1 then
|
|
begin
|
|
fObject := varObjPairs[1];
|
|
PPointer(varObjPairs[0])^ := fObject;
|
|
exit;
|
|
end;
|
|
SetLength(fObjectList, n);
|
|
for i := 0 to n - 1 do
|
|
begin
|
|
fObjectList[i] := varObjPairs[i * 2 + 1];
|
|
PPointer(varObjPairs[i * 2])^ := fObjectList[i];
|
|
end;
|
|
end;
|
|
|
|
procedure TAutoFree.ForMethod;
|
|
begin
|
|
// do-nothing method to circumvent the Delphi 10.4 IAutoFree early release
|
|
end;
|
|
|
|
class function TAutoFree.One(var localVariable; obj: TObject): IAutoFree;
|
|
begin
|
|
result := Create(localVariable,obj);
|
|
{$ifdef ISDELPHI104}
|
|
result.ForMethod;
|
|
{$endif ISDELPHI104}
|
|
end;
|
|
|
|
class function TAutoFree.Several(const varObjPairs: array of pointer): IAutoFree;
|
|
begin
|
|
result := Create(varObjPairs);
|
|
// inlining is not possible on Delphi -> Delphi 10.4 caller should run ForMethod :(
|
|
end;
|
|
|
|
procedure TAutoFree.Another(var localVariable; obj: TObject);
|
|
var
|
|
n: PtrInt;
|
|
begin
|
|
n := length(fObjectList);
|
|
SetLength(fObjectList, n + 1);
|
|
fObjectList[n] := obj;
|
|
TObject(localVariable) := obj;
|
|
end;
|
|
|
|
destructor TAutoFree.Destroy;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
if fObjectList <> nil then
|
|
for i := length(fObjectList) - 1 downto 0 do // release FILO
|
|
fObjectList[i].Free;
|
|
fObject.Free;
|
|
inherited;
|
|
end;
|
|
|
|
|
|
{ TAutoLocker }
|
|
|
|
constructor TAutoLocker.Create;
|
|
begin
|
|
fSafe.Init;
|
|
end;
|
|
|
|
destructor TAutoLocker.Destroy;
|
|
begin
|
|
fSafe.Done;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TAutoLocker.ProtectMethod: IUnknown;
|
|
begin
|
|
result := TAutoLock.Create(@fSafe);
|
|
end;
|
|
|
|
procedure TAutoLocker.Enter;
|
|
begin
|
|
fSafe.Lock;
|
|
end;
|
|
|
|
procedure TAutoLocker.Leave;
|
|
begin
|
|
fSafe.UnLock;
|
|
end;
|
|
|
|
function TAutoLocker.Safe: PSynLocker;
|
|
begin
|
|
result := @fSafe;
|
|
end;
|
|
|
|
|
|
{ ************ TSynPersistent* / TSyn*List / TSynLocker classes }
|
|
|
|
{ TSynPersistent }
|
|
|
|
procedure TSynPersistent.AssignError(Source: TSynPersistent);
|
|
begin
|
|
raise EConvertError.CreateFmt('Cannot assign a %s to a %s',
|
|
[ClassNameShort(Source)^, ClassNameShort(self)^]);
|
|
end;
|
|
|
|
procedure TSynPersistent.AssignTo(Dest: TSynPersistent);
|
|
begin
|
|
Dest.AssignError(Self);
|
|
end;
|
|
|
|
procedure TSynPersistent.Assign(Source: TSynPersistent);
|
|
begin
|
|
if Source <> nil then
|
|
Source.AssignTo(Self)
|
|
else
|
|
AssignError(nil);
|
|
end;
|
|
|
|
{$ifdef HASITERATORS}
|
|
|
|
{ TPointerEnumerator }
|
|
|
|
procedure TPointerEnumerator.Init(Values: PPointerArray; Count: PtrUInt);
|
|
begin
|
|
if Count = 0 then
|
|
begin
|
|
Curr := nil;
|
|
After := nil;
|
|
end
|
|
else
|
|
begin
|
|
Curr := pointer(Values);
|
|
After := @Values[Count];
|
|
dec(Curr);
|
|
end;
|
|
end;
|
|
|
|
function TPointerEnumerator.MoveNext: Boolean;
|
|
begin
|
|
inc(Curr);
|
|
result := PtrUInt(Curr) < PtrUInt(After);
|
|
end;
|
|
|
|
function TPointerEnumerator.GetCurrent: pointer;
|
|
begin
|
|
result := Curr^;
|
|
end;
|
|
|
|
function TPointerEnumerator.GetEnumerator: TPointerEnumerator;
|
|
begin
|
|
result := self;
|
|
end;
|
|
|
|
{$endif HASITERATORS}
|
|
|
|
{ TSynList }
|
|
|
|
constructor TSynList.Create;
|
|
begin
|
|
// nothing to do
|
|
end;
|
|
|
|
function TSynList.Add(item: pointer): PtrInt;
|
|
begin
|
|
// inlined result := ObjArrayAddCount(fList, item, fCount);
|
|
result := fCount;
|
|
if result = length(fList) then
|
|
SetLength(fList, NextGrow(result));
|
|
fList[result] := item;
|
|
inc(fCount);
|
|
end;
|
|
|
|
function TSynList.Insert(item: pointer; index: PtrInt): PtrInt;
|
|
begin
|
|
result := PtrArrayInsert(fList, item, index, fCount);
|
|
end;
|
|
|
|
procedure TSynList.Clear;
|
|
begin
|
|
fList := nil;
|
|
fCount := 0;
|
|
end;
|
|
|
|
procedure TSynList.Delete(index: integer; dontfree: boolean);
|
|
begin
|
|
PtrArrayDelete(fList, index, @fCount);
|
|
if (fCount > 64) and
|
|
(length(fList) > fCount * 2) then
|
|
SetLength(fList, fCount); // reduce capacity when half list is void
|
|
end;
|
|
|
|
function TSynList.Exists(item: pointer): boolean;
|
|
begin
|
|
result := IndexOf(item) >= 0;
|
|
end;
|
|
|
|
function TSynList.Get(index: integer): pointer;
|
|
begin
|
|
if cardinal(index) < cardinal(fCount) then
|
|
result := fList[index]
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
function TSynList.IndexOf(item: pointer): PtrInt;
|
|
begin
|
|
result := PtrUIntScanIndex(pointer(fList), fCount, PtrUInt(item));
|
|
end;
|
|
|
|
function TSynList.Remove(item: pointer): PtrInt;
|
|
begin
|
|
result := IndexOf(item);
|
|
if result >= 0 then
|
|
Delete(result);
|
|
end;
|
|
|
|
{$ifdef HASITERATORS}
|
|
|
|
function TSynList.GetEnumerator: TPointerEnumerator;
|
|
begin
|
|
result.Init(pointer(fList), fCount);
|
|
end;
|
|
|
|
{$endif HASITERATORS}
|
|
|
|
|
|
{ TSynObjectList }
|
|
|
|
constructor TSynObjectList.Create(aOwnObjects: boolean; aItemClass: TClass);
|
|
begin
|
|
fOwnObjects := aOwnObjects;
|
|
fItemClass := aItemClass;
|
|
inherited Create;
|
|
end;
|
|
|
|
procedure TSynObjectList.Delete(index: integer; dontfree: boolean);
|
|
begin
|
|
if cardinal(index) >= cardinal(fCount) then
|
|
exit;
|
|
if fOwnObjects and
|
|
not dontfree then
|
|
TObject(fList[index]).Free;
|
|
inherited Delete(index);
|
|
end;
|
|
|
|
procedure TSynObjectList.Clear;
|
|
begin
|
|
if fOwnObjects then
|
|
RawObjectsClear(pointer(fList), fCount);
|
|
inherited Clear;
|
|
end;
|
|
|
|
procedure TSynObjectList.ClearFromLast;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
if fOwnObjects then
|
|
for i := fCount - 1 downto 0 do // call Free in reverse order
|
|
FreeAndNilSafe(fList[i]); // safer
|
|
inherited Clear;
|
|
end;
|
|
|
|
destructor TSynObjectList.Destroy;
|
|
begin
|
|
Clear;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TSynObjectList.NewItem: pointer;
|
|
begin
|
|
result := nil;
|
|
if fItemClass = nil then
|
|
exit;
|
|
result := Rtti.RegisterClass(fItemClass).ClassNewInstance;
|
|
Add(result);
|
|
end;
|
|
|
|
|
|
{ TSynPersistentLock }
|
|
|
|
constructor TSynPersistentLock.Create;
|
|
begin
|
|
inherited Create; // may have been overriden
|
|
fSafe := NewSynLocker;
|
|
end;
|
|
|
|
destructor TSynPersistentLock.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
fSafe^.DoneAndFreeMem;
|
|
end;
|
|
|
|
procedure TSynPersistentLock.Lock;
|
|
begin
|
|
if self <> nil then
|
|
fSafe^.Lock;
|
|
end;
|
|
|
|
procedure TSynPersistentLock.Unlock;
|
|
begin
|
|
if self <> nil then
|
|
fSafe^.UnLock;
|
|
end;
|
|
|
|
class procedure TSynPersistentLock.RttiCustomSetParser(Rtti: TRttiCustom);
|
|
begin
|
|
// let's call our overriden RttiBeforeWriteObject and RttiAfterWriteObject
|
|
Rtti.Flags := Rtti.Flags + [rcfHookWrite];
|
|
end;
|
|
|
|
function TSynPersistentLock.RttiBeforeWriteObject(W: TTextWriter;
|
|
var Options: TTextWriterWriteObjectOptions): boolean;
|
|
begin
|
|
if woPersistentLock in Options then
|
|
fSafe.Lock;
|
|
result := false; // continue with default JSON serialization
|
|
end;
|
|
|
|
procedure TSynPersistentLock.RttiAfterWriteObject(W: TTextWriter;
|
|
Options: TTextWriterWriteObjectOptions);
|
|
begin
|
|
if woPersistentLock in Options then
|
|
fSafe.UnLock;
|
|
end;
|
|
|
|
{ TInterfacedObjectLocked }
|
|
|
|
constructor TInterfacedObjectLocked.Create;
|
|
begin
|
|
inherited Create;
|
|
fSafe := NewSynLocker;
|
|
end;
|
|
|
|
destructor TInterfacedObjectLocked.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
fSafe^.DoneAndFreeMem;
|
|
end;
|
|
|
|
|
|
{ TSynObjectListLocked }
|
|
|
|
function TSynObjectListLocked.Add(item: pointer): PtrInt;
|
|
begin
|
|
Safe.WriteLock;
|
|
try
|
|
result := inherited Add(item);
|
|
finally
|
|
Safe.WriteUnLock;
|
|
end;
|
|
end;
|
|
|
|
function TSynObjectListLocked.Remove(item: pointer): PtrInt;
|
|
begin
|
|
Safe.WriteLock;
|
|
try
|
|
result := inherited Remove(item);
|
|
finally
|
|
Safe.WriteUnLock;
|
|
end;
|
|
end;
|
|
|
|
function TSynObjectListLocked.Exists(item: pointer): boolean;
|
|
begin
|
|
Safe.ReadOnlyLock;
|
|
try
|
|
result := inherited Exists(item);
|
|
finally
|
|
Safe.ReadOnlyUnLock;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynObjectListLocked.Clear;
|
|
begin
|
|
Safe.WriteLock;
|
|
try
|
|
inherited Clear;
|
|
finally
|
|
Safe.WriteUnLock;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynObjectListLocked.ClearFromLast;
|
|
begin
|
|
Safe.WriteLock;
|
|
try
|
|
inherited ClearFromLast;
|
|
finally
|
|
Safe.WriteUnLock;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TSynObjectListSorted }
|
|
|
|
constructor TSynObjectListSorted.Create(const aCompare: TOnObjectCompare;
|
|
aOwnsObjects: boolean);
|
|
begin
|
|
inherited Create(aOwnsObjects);
|
|
fCompare := aCompare;
|
|
end;
|
|
|
|
function TSynObjectListSorted.Locate(item: pointer; out index: PtrInt): boolean;
|
|
var
|
|
n, l, i: PtrInt;
|
|
cmp: integer;
|
|
begin // see TDynArray.FastLocateSorted below
|
|
result := false;
|
|
n := fCount;
|
|
if n = 0 then // a void array is always sorted
|
|
index := 0
|
|
else
|
|
begin
|
|
dec(n);
|
|
cmp := fCompare(fList[n], item);
|
|
if cmp <= 0 then
|
|
begin
|
|
// greater than last sorted item (may be a common case)
|
|
if cmp = 0 then
|
|
// returns true + index of existing item
|
|
result := true
|
|
else
|
|
// returns false + insert after last position
|
|
inc(n);
|
|
index := n;
|
|
exit;
|
|
end;
|
|
l := 0;
|
|
repeat
|
|
// O(log(n)) binary search of the sorted position
|
|
i := (l + n) shr 1;
|
|
cmp := fCompare(fList[i], item);
|
|
if cmp = 0 then
|
|
begin
|
|
// returns true + index of existing item
|
|
index := i;
|
|
result := true;
|
|
exit;
|
|
end
|
|
else if cmp < 0 then
|
|
l := i + 1
|
|
else
|
|
n := i - 1;
|
|
until l > n;
|
|
// item not found: returns false + the index where to insert
|
|
index := l;
|
|
end;
|
|
end;
|
|
|
|
function TSynObjectListSorted.Add(item: pointer): PtrInt;
|
|
begin
|
|
Safe.WriteLock;
|
|
try
|
|
if Locate(item, result) then // O(log(n)) binary search
|
|
result := -(result + 1)
|
|
else
|
|
Insert(item, result);
|
|
finally
|
|
Safe.WriteUnLock;
|
|
end;
|
|
end;
|
|
|
|
function TSynObjectListSorted.IndexOf(item: pointer): PtrInt;
|
|
begin
|
|
if not Locate(item, result) then // O(log(n)) binary search
|
|
result := -1;
|
|
end;
|
|
|
|
function TSynObjectListSorted.Find(item: TObject): TObject;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
if Locate(item, i) then
|
|
result := fList[i]
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
|
|
{ ************ TSynPersistentStore with proper Binary Serialization }
|
|
|
|
{ TSynPersistentStore }
|
|
|
|
constructor TSynPersistentStore.Create(const aName: RawUtf8);
|
|
begin
|
|
inherited Create; // may have been overriden
|
|
fName := aName;
|
|
end;
|
|
|
|
constructor TSynPersistentStore.CreateFrom(const aBuffer: RawByteString;
|
|
aLoad: TAlgoCompressLoad);
|
|
begin
|
|
CreateFromBuffer(pointer(aBuffer), length(aBuffer), aLoad);
|
|
end;
|
|
|
|
constructor TSynPersistentStore.CreateFromBuffer(
|
|
aBuffer: pointer; aBufferLen: integer; aLoad: TAlgoCompressLoad);
|
|
begin
|
|
inherited Create; // may have been overriden
|
|
LoadFrom(aBuffer, aBufferLen, aLoad);
|
|
end;
|
|
|
|
constructor TSynPersistentStore.CreateFromFile(const aFileName: TFileName;
|
|
aLoad: TAlgoCompressLoad);
|
|
begin
|
|
inherited Create; // may have been overriden
|
|
LoadFromFile(aFileName, aLoad);
|
|
end;
|
|
|
|
procedure TSynPersistentStore.LoadFromReader;
|
|
begin
|
|
fReader.VarUtf8(fName);
|
|
end;
|
|
|
|
procedure TSynPersistentStore.SaveToWriter(aWriter: TBufferWriter);
|
|
begin
|
|
aWriter.Write(fName);
|
|
end;
|
|
|
|
procedure TSynPersistentStore.LoadFrom(const aBuffer: RawByteString;
|
|
aLoad: TAlgoCompressLoad);
|
|
begin
|
|
if aBuffer <> '' then
|
|
LoadFrom(pointer(aBuffer), length(aBuffer), aLoad);
|
|
end;
|
|
|
|
procedure TSynPersistentStore.LoadFrom(aBuffer: pointer; aBufferLen: integer;
|
|
aLoad: TAlgoCompressLoad);
|
|
var
|
|
localtemp: RawByteString;
|
|
p: pointer;
|
|
temp: PRawByteString;
|
|
begin
|
|
if (aBuffer = nil) or
|
|
(aBufferLen <= 0) then
|
|
exit; // nothing to load
|
|
fLoadFromLastAlgo := TAlgoCompress.Algo(aBuffer, aBufferLen);
|
|
if fLoadFromLastAlgo = nil then
|
|
fReader.ErrorData('%.LoadFrom unknown TAlgoCompress AlgoID=%',
|
|
[self, PByteArray(aBuffer)[4]]);
|
|
temp := fReaderTemp;
|
|
if temp = nil then
|
|
temp := @localtemp;
|
|
p := fLoadFromLastAlgo.Decompress(aBuffer, aBufferLen,
|
|
fLoadFromLastUncompressed, temp^, aLoad);
|
|
if p = nil then
|
|
fReader.ErrorData('%.LoadFrom %.Decompress failed',
|
|
[self, fLoadFromLastAlgo]);
|
|
fReader.Init(p, fLoadFromLastUncompressed);
|
|
LoadFromReader;
|
|
end;
|
|
|
|
function TSynPersistentStore.LoadFromFile(const aFileName: TFileName;
|
|
aLoad: TAlgoCompressLoad): boolean;
|
|
var
|
|
temp: RawByteString;
|
|
begin
|
|
temp := StringFromFile(aFileName);
|
|
result := temp <> '';
|
|
if result then
|
|
LoadFrom(temp, aLoad);
|
|
end;
|
|
|
|
procedure TSynPersistentStore.SaveTo(out aBuffer: RawByteString;
|
|
nocompression: boolean; BufLen: integer; ForcedAlgo: TAlgoCompress;
|
|
BufferOffset: integer);
|
|
var
|
|
writer: TBufferWriter;
|
|
temp: array[word] of byte;
|
|
begin
|
|
if BufLen <= SizeOf(temp) then
|
|
writer := TBufferWriter.Create(TRawByteStringStream, @temp, SizeOf(temp))
|
|
else
|
|
writer := TBufferWriter.Create(TRawByteStringStream, BufLen);
|
|
try
|
|
SaveToWriter(writer);
|
|
fSaveToLastUncompressed := writer.TotalWritten;
|
|
aBuffer := writer.FlushAndCompress(nocompression, ForcedAlgo, BufferOffset);
|
|
finally
|
|
writer.Free;
|
|
end;
|
|
end;
|
|
|
|
function TSynPersistentStore.SaveTo(nocompression: boolean; BufLen: integer;
|
|
ForcedAlgo: TAlgoCompress; BufferOffset: integer): RawByteString;
|
|
begin
|
|
SaveTo(result, nocompression, BufLen, ForcedAlgo, BufferOffset);
|
|
end;
|
|
|
|
function TSynPersistentStore.SaveToFile(const aFileName: TFileName;
|
|
nocompression: boolean; BufLen: integer; ForcedAlgo: TAlgoCompress): PtrUInt;
|
|
var
|
|
temp: RawByteString;
|
|
begin
|
|
SaveTo(temp, nocompression, BufLen, ForcedAlgo);
|
|
if FileFromString(temp, aFileName) then
|
|
result := length(temp)
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
|
|
|
|
|
|
{ ************ INI Files and In-memory Access }
|
|
|
|
function IdemPChar2(table: PNormTable; p: PUtf8Char; up: PAnsiChar): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
var
|
|
u: AnsiChar;
|
|
begin
|
|
// here p and up are expected to be <> nil
|
|
result := false;
|
|
dec(PtrUInt(p), PtrUInt(up));
|
|
repeat
|
|
u := up^;
|
|
if u = #0 then
|
|
break;
|
|
if table^[up[PtrUInt(p)]] <> u then
|
|
exit;
|
|
inc(up);
|
|
until false;
|
|
result := true;
|
|
end;
|
|
|
|
function FindSectionFirstLine(var source: PUtf8Char; search: PAnsiChar): boolean;
|
|
var
|
|
table: PNormTable;
|
|
charset: PTextCharSet;
|
|
begin
|
|
result := false;
|
|
if (source = nil) or
|
|
(search = nil) then
|
|
exit;
|
|
table := @NormToUpperAnsi7;
|
|
charset := @TEXT_CHARS;
|
|
repeat
|
|
if source^ = '[' then
|
|
begin
|
|
inc(source);
|
|
result := IdemPChar2(table, source, search);
|
|
end;
|
|
while tcNot01013 in charset[source^] do
|
|
inc(source);
|
|
while tc1013 in charset[source^] do
|
|
inc(source);
|
|
if result then
|
|
exit; // found
|
|
until source^ = #0;
|
|
source := nil;
|
|
end;
|
|
|
|
function FindSectionFirstLineW(var source: PWideChar; search: PUtf8Char): boolean;
|
|
begin
|
|
result := false;
|
|
if source = nil then
|
|
exit;
|
|
repeat
|
|
if source^ = '[' then
|
|
begin
|
|
inc(source);
|
|
result := IdemPCharW(source, search);
|
|
end;
|
|
while not (cardinal(source^) in [0, 10, 13]) do
|
|
inc(source);
|
|
while cardinal(source^) in [10, 13] do
|
|
inc(source);
|
|
if result then
|
|
exit; // found
|
|
until source^ = #0;
|
|
source := nil;
|
|
end;
|
|
|
|
function FindIniNameValue(P: PUtf8Char; UpperName: PAnsiChar;
|
|
const DefaultValue: RawUtf8): RawUtf8;
|
|
var
|
|
u, PBeg: PUtf8Char;
|
|
by4: cardinal;
|
|
{$ifdef CPUX86NOTPIC}
|
|
table: TNormTable absolute NormToUpperAnsi7;
|
|
{$else}
|
|
table: PNormTable;
|
|
{$endif CPUX86NOTPIC}
|
|
begin
|
|
// expect UpperName as 'NAME='
|
|
if (P <> nil) and
|
|
(P^ <> '[') and
|
|
(UpperName <> nil) then
|
|
begin
|
|
{$ifndef CPUX86NOTPIC}
|
|
table := @NormToUpperAnsi7;
|
|
{$endif CPUX86NOTPIC}
|
|
PBeg := nil;
|
|
u := P;
|
|
repeat
|
|
while u^ = ' ' do
|
|
inc(u); // trim left ' '
|
|
if u^ = #0 then
|
|
break;
|
|
if table[u^] = UpperName[0] then
|
|
PBeg := u;
|
|
repeat
|
|
by4 := PCardinal(u)^;
|
|
if ToByte(by4) > 13 then
|
|
if ToByte(by4 shr 8) > 13 then
|
|
if ToByte(by4 shr 16) > 13 then
|
|
if ToByte(by4 shr 24) > 13 then
|
|
begin
|
|
inc(u, 4);
|
|
continue;
|
|
end
|
|
else
|
|
inc(u, 3)
|
|
else
|
|
inc(u, 2)
|
|
else
|
|
inc(u);
|
|
if u^ in [#0, #10, #13] then
|
|
break;
|
|
inc(u);
|
|
until false;
|
|
if PBeg <> nil then
|
|
begin
|
|
inc(PBeg);
|
|
P := u;
|
|
u := pointer(UpperName + 1);
|
|
repeat
|
|
if u^ <> #0 then
|
|
if table[PBeg^] <> u^ then
|
|
break
|
|
else
|
|
begin
|
|
inc(u);
|
|
inc(PBeg);
|
|
end
|
|
else
|
|
begin
|
|
FastSetString(result, PBeg, P - PBeg);
|
|
exit;
|
|
end;
|
|
until false;
|
|
PBeg := nil;
|
|
u := P;
|
|
end;
|
|
if u^ = #13 then
|
|
inc(u);
|
|
if u^ = #10 then
|
|
inc(u);
|
|
until u^ in [#0, '['];
|
|
end;
|
|
result := DefaultValue;
|
|
end;
|
|
|
|
function ExistsIniName(P: PUtf8Char; UpperName: PAnsiChar): boolean;
|
|
var
|
|
table: PNormTable;
|
|
begin
|
|
result := false;
|
|
if (P <> nil) and
|
|
(P^ <> '[') then
|
|
begin
|
|
table := @NormToUpperAnsi7;
|
|
repeat
|
|
if P^ = ' ' then
|
|
begin
|
|
repeat
|
|
inc(P)
|
|
until P^ <> ' '; // trim left ' '
|
|
if P^ = #0 then
|
|
break;
|
|
end;
|
|
if IdemPChar2(table, P, UpperName) then
|
|
begin
|
|
result := true;
|
|
exit;
|
|
end;
|
|
repeat
|
|
if P[0] > #13 then
|
|
if P[1] > #13 then
|
|
if P[2] > #13 then
|
|
if P[3] > #13 then
|
|
begin
|
|
inc(P, 4);
|
|
continue;
|
|
end
|
|
else
|
|
inc(P, 3)
|
|
else
|
|
inc(P, 2)
|
|
else
|
|
inc(P);
|
|
case P^ of
|
|
#0:
|
|
exit;
|
|
#10:
|
|
begin
|
|
inc(P);
|
|
break;
|
|
end;
|
|
#13:
|
|
begin
|
|
if P[1] = #10 then
|
|
inc(P, 2)
|
|
else
|
|
inc(P);
|
|
break;
|
|
end;
|
|
else
|
|
inc(P);
|
|
end;
|
|
until false;
|
|
until P^ = '[';
|
|
end;
|
|
end;
|
|
|
|
function ExistsIniNameValue(P: PUtf8Char; const UpperName: RawUtf8;
|
|
UpperValues: PPAnsiChar): boolean;
|
|
var
|
|
table: PNormTable;
|
|
begin
|
|
if (UpperValues <> nil) and
|
|
(UpperValues^ <> nil) and
|
|
(UpperName <> '') then
|
|
begin
|
|
result := true;
|
|
table := @NormToUpperAnsi7;
|
|
while (P <> nil) and
|
|
(P^ <> '[') do
|
|
begin
|
|
if P^ = ' ' then
|
|
repeat
|
|
inc(P)
|
|
until P^ <> ' '; // trim left ' '
|
|
if IdemPChar2(table, P, pointer(UpperName)) then
|
|
begin
|
|
inc(P, length(UpperName));
|
|
if IdemPPChar(P, UpperValues) >= 0 then
|
|
exit; // found one value
|
|
break;
|
|
end;
|
|
P := GotoNextLine(P);
|
|
end;
|
|
end;
|
|
result := false;
|
|
end;
|
|
|
|
function GetSectionContent(SectionFirstLine: PUtf8Char): RawUtf8;
|
|
var
|
|
PBeg: PUtf8Char;
|
|
begin
|
|
PBeg := SectionFirstLine;
|
|
while (SectionFirstLine <> nil) and
|
|
(SectionFirstLine^ <> '[') do
|
|
SectionFirstLine := GotoNextLine(SectionFirstLine);
|
|
if SectionFirstLine = nil then
|
|
result := PBeg
|
|
else
|
|
FastSetString(result, PBeg, SectionFirstLine - PBeg);
|
|
end;
|
|
|
|
function GetSectionContent(const Content, SectionName: RawUtf8): RawUtf8;
|
|
var
|
|
P: PUtf8Char;
|
|
UpperSection: array[byte] of AnsiChar;
|
|
begin
|
|
P := pointer(Content);
|
|
PWord(UpperCopy255(UpperSection{%H-}, SectionName))^ := ord(']');
|
|
if FindSectionFirstLine(P, UpperSection) then
|
|
result := GetSectionContent(P)
|
|
else
|
|
result := '';
|
|
end;
|
|
|
|
function DeleteSection(var Content: RawUtf8; const SectionName: RawUtf8;
|
|
EraseSectionHeader: boolean): boolean;
|
|
var
|
|
P: PUtf8Char;
|
|
UpperSection: array[byte] of AnsiChar;
|
|
begin
|
|
result := false; // no modification
|
|
P := pointer(Content);
|
|
PWord(UpperCopy255(UpperSection{%H-}, SectionName))^ := ord(']');
|
|
if FindSectionFirstLine(P, UpperSection) then
|
|
result := DeleteSection(P, Content, EraseSectionHeader);
|
|
end;
|
|
|
|
function DeleteSection(SectionFirstLine: PUtf8Char; var Content: RawUtf8;
|
|
EraseSectionHeader: boolean): boolean;
|
|
var
|
|
PEnd: PUtf8Char;
|
|
IndexBegin: PtrInt;
|
|
begin
|
|
result := false;
|
|
PEnd := SectionFirstLine;
|
|
if EraseSectionHeader then // erase [Section] header line
|
|
while (PtrUInt(SectionFirstLine) > PtrUInt(Content)) and
|
|
(SectionFirstLine^ <> '[') do
|
|
dec(SectionFirstLine);
|
|
while (PEnd <> nil) and
|
|
(PEnd^ <> '[') do
|
|
PEnd := GotoNextLine(PEnd);
|
|
IndexBegin := SectionFirstLine - pointer(Content);
|
|
if IndexBegin = 0 then
|
|
exit; // no modification
|
|
if PEnd = nil then
|
|
SetLength(Content, IndexBegin)
|
|
else
|
|
delete(Content, IndexBegin + 1, PEnd - SectionFirstLine);
|
|
result := true; // Content was modified
|
|
end;
|
|
|
|
procedure ReplaceSection(SectionFirstLine: PUtf8Char; var Content: RawUtf8;
|
|
const NewSectionContent: RawUtf8);
|
|
var
|
|
PEnd: PUtf8Char;
|
|
IndexBegin: PtrInt;
|
|
begin
|
|
if SectionFirstLine = nil then
|
|
exit;
|
|
// delete existing [Section] content
|
|
PEnd := SectionFirstLine;
|
|
while (PEnd <> nil) and
|
|
(PEnd^ <> '[') do
|
|
PEnd := GotoNextLine(PEnd);
|
|
IndexBegin := SectionFirstLine - pointer(Content);
|
|
if PEnd = nil then
|
|
SetLength(Content, IndexBegin)
|
|
else
|
|
delete(Content, IndexBegin + 1, PEnd - SectionFirstLine);
|
|
// insert section content
|
|
insert(NewSectionContent, Content, IndexBegin + 1);
|
|
end;
|
|
|
|
procedure ReplaceSection(var Content: RawUtf8; const SectionName, NewSectionContent: RawUtf8);
|
|
var
|
|
UpperSection: array[byte] of AnsiChar;
|
|
P: PUtf8Char;
|
|
begin
|
|
P := pointer(Content);
|
|
PWord(UpperCopy255(UpperSection{%H-}, SectionName))^ := ord(']');
|
|
if FindSectionFirstLine(P, UpperSection) then
|
|
ReplaceSection(P, Content, NewSectionContent)
|
|
else
|
|
Content := Content + '[' + SectionName + ']'#13#10 + NewSectionContent;
|
|
end;
|
|
|
|
function FindIniNameValueInteger(P: PUtf8Char; const UpperName: RawUtf8): PtrInt;
|
|
var
|
|
table: PNormTable;
|
|
begin
|
|
result := 0;
|
|
if (P = nil) or
|
|
(UpperName = '') then
|
|
exit;
|
|
table := @NormToUpperAnsi7;
|
|
repeat
|
|
if IdemPChar2(table, P, pointer(UpperName)) then
|
|
break;
|
|
P := GotoNextLine(P);
|
|
if P = nil then
|
|
exit;
|
|
until false;
|
|
result := GetInteger(P + length(UpperName));
|
|
end;
|
|
|
|
function FindIniEntry(const Content, Section, Name, DefaultValue: RawUtf8): RawUtf8;
|
|
var
|
|
P: PUtf8Char;
|
|
UpperSection, UpperName: array[byte] of AnsiChar;
|
|
begin
|
|
result := DefaultValue;
|
|
P := pointer(Content);
|
|
if P = nil then
|
|
exit;
|
|
// fast UpperName := UpperCase(Name)+'='
|
|
PWord(UpperCopy255(UpperName{%H-}, Name))^ := ord('=');
|
|
if Section = '' then
|
|
// find the Name= entry before any [Section]
|
|
result := FindIniNameValue(P, UpperName, DefaultValue)
|
|
else
|
|
begin
|
|
// find the Name= entry in the specified [Section]
|
|
PWord(UpperCopy255(UpperSection{%H-}, Section))^ := ord(']');
|
|
if FindSectionFirstLine(P, UpperSection) then
|
|
result := FindIniNameValue(P, UpperName, DefaultValue);
|
|
end;
|
|
end;
|
|
|
|
function FindWinAnsiIniEntry(const Content, Section, Name: RawUtf8): RawUtf8;
|
|
begin
|
|
result := WinAnsiToUtf8(WinAnsiString(FindIniEntry(Content, Section, Name)));
|
|
end;
|
|
|
|
function FindIniEntryInteger(const Content, Section, Name: RawUtf8): integer;
|
|
begin
|
|
result := GetInteger(pointer(FindIniEntry(Content, Section, Name)));
|
|
end;
|
|
|
|
function FindIniEntryFile(const FileName: TFileName;
|
|
const Section, Name, DefaultValue: RawUtf8): RawUtf8;
|
|
var
|
|
Content: RawUtf8;
|
|
begin
|
|
Content := StringFromFile(FileName);
|
|
if Content = '' then
|
|
result := DefaultValue
|
|
else
|
|
result := FindIniEntry(Content, Section, Name, DefaultValue);
|
|
end;
|
|
|
|
function UpdateIniNameValueInternal(var Content: RawUtf8;
|
|
const NewValue, NewValueCRLF: RawUtf8;
|
|
var P: PUtf8Char; UpperName: PAnsiChar; UpperNameLength: integer): boolean;
|
|
var
|
|
PBeg: PUtf8Char;
|
|
i: integer;
|
|
begin
|
|
if UpperName <> nil then
|
|
while (P <> nil) and
|
|
(P^ <> '[') do
|
|
begin
|
|
while P^ = ' ' do
|
|
inc(P); // trim left ' '
|
|
PBeg := P;
|
|
P := GotoNextLine(P);
|
|
if IdemPChar2(@NormToUpperAnsi7, PBeg, UpperName) then
|
|
begin
|
|
// update Name=Value entry
|
|
result := true;
|
|
inc(PBeg, UpperNameLength);
|
|
i := (PBeg - pointer(Content)) + 1;
|
|
if (i = length(NewValue)) and
|
|
CompareMem(PBeg, pointer(NewValue), i) then
|
|
exit; // new Value is identical to the old one -> no change
|
|
if P = nil then // avoid last line (P-PBeg) calculation error
|
|
SetLength(Content, i - 1)
|
|
else
|
|
delete(Content, i, P - PBeg); // delete old Value
|
|
insert(NewValueCRLF, Content, i); // set new value
|
|
exit;
|
|
end;
|
|
end;
|
|
result := false;
|
|
end;
|
|
|
|
function UpdateIniNameValue(var Content: RawUtf8;
|
|
const Name, UpperName, NewValue: RawUtf8): boolean;
|
|
var
|
|
P: PUtf8Char;
|
|
begin
|
|
if UpperName = '' then
|
|
result := false
|
|
else
|
|
begin
|
|
P := pointer(Content);
|
|
result := UpdateIniNameValueInternal(Content, NewValue, NewValue + #13#10,
|
|
P, pointer(UpperName), length(UpperName));
|
|
if result or
|
|
(Name = '') then
|
|
exit;
|
|
AppendLine(Content, [Name, NewValue]);
|
|
result := true;
|
|
end;
|
|
end;
|
|
|
|
procedure UpdateIniEntry(var Content: RawUtf8;
|
|
const Section, Name, Value: RawUtf8);
|
|
const
|
|
CRLF = #13#10;
|
|
var
|
|
P: PUtf8Char;
|
|
SectionFound: boolean;
|
|
i, UpperNameLength: PtrInt;
|
|
V: RawUtf8;
|
|
UpperSection, UpperName: array[byte] of AnsiChar;
|
|
begin
|
|
UpperNameLength := length(Name);
|
|
PWord(UpperCopy255Buf(
|
|
UpperName{%H-}, pointer(Name), UpperNameLength))^ := ord('=');
|
|
inc(UpperNameLength);
|
|
V := Value + CRLF;
|
|
P := pointer(Content);
|
|
// 1. find Section, and try update within it
|
|
if Section = '' then
|
|
SectionFound := true // find the Name= entry before any [Section]
|
|
else
|
|
begin
|
|
PWord(UpperCopy255(UpperSection{%H-}, Section))^ := ord(']');
|
|
SectionFound := FindSectionFirstLine(P, UpperSection);
|
|
end;
|
|
if SectionFound and
|
|
UpdateIniNameValueInternal(
|
|
Content, Value, V, P, @UpperName, UpperNameLength) then
|
|
exit;
|
|
// 2. section or Name= entry not found: add Name=Value
|
|
V := Name + '=' + V;
|
|
if not SectionFound then
|
|
// create not existing [Section]
|
|
V := '[' + Section + (']' + CRLF) + V;
|
|
// insert Name=Value at P^ (end of file or end of [Section])
|
|
if P = nil then
|
|
// insert at end of file
|
|
Content := Content + V
|
|
else
|
|
begin
|
|
// insert at end of [Section]
|
|
i := (P - pointer(Content)) + 1;
|
|
insert(V, Content, i);
|
|
end;
|
|
end;
|
|
|
|
procedure UpdateIniEntryFile(const FileName: TFileName;
|
|
const Section, Name, Value: RawUtf8);
|
|
var
|
|
Content: RawUtf8;
|
|
begin
|
|
Content := StringFromFile(FileName);
|
|
UpdateIniEntry(Content, Section, Name, Value);
|
|
FileFromString(Content, FileName);
|
|
end;
|
|
|
|
function IsHtmlContentTypeTextual(Headers: PUtf8Char): boolean;
|
|
begin
|
|
result := ExistsIniNameValue(Headers, HEADER_CONTENT_TYPE_UPPER, @CONTENT_TYPE_TEXTUAL);
|
|
end;
|
|
|
|
const
|
|
WS_UPGRADE: array[0..2] of PAnsiChar = (
|
|
'UPGRADE',
|
|
'KEEP-ALIVE, UPGRADE',
|
|
nil);
|
|
|
|
function IsWebSocketUpgrade(headers: PUtf8Char): boolean;
|
|
begin
|
|
result := ExistsIniNameValue(pointer(headers), 'CONNECTION: ', @WS_UPGRADE);
|
|
end;
|
|
|
|
function IniToObject(const Ini: RawUtf8; Instance: TObject;
|
|
const SectionName: RawUtf8; DocVariantOptions: PDocVariantOptions;
|
|
Level: integer): boolean;
|
|
var
|
|
r: TRttiCustom;
|
|
i: integer;
|
|
p: PRttiCustomProp;
|
|
section, nested, json: PUtf8Char;
|
|
name: PAnsiChar;
|
|
n, v: RawUtf8;
|
|
up: array[byte] of AnsiChar;
|
|
begin
|
|
result := false; // true when at least one property has been read
|
|
if (Ini = '') or
|
|
(Instance = nil) then
|
|
exit;
|
|
PWord(UpperCopy255(up{%H-}, SectionName))^ := ord(']');
|
|
section := pointer(Ini);
|
|
if not FindSectionFirstLine(section, @up) then
|
|
exit; // section not found
|
|
r := Rtti.RegisterClass(Instance);
|
|
p := pointer(r.Props.List);
|
|
for i := 1 to r.Props.Count do
|
|
begin
|
|
if p^.Prop <> nil then
|
|
if p^.Value.Kind = rkClass then
|
|
begin
|
|
// recursive load from another per-property section
|
|
if Level = 0 then
|
|
n := p^.Name
|
|
else
|
|
n := SectionName + '.' + p^.Name;
|
|
if IniToObject(Ini, p^.Prop^.GetObjProp(Instance), n,
|
|
DocVariantOptions, Level + 1) then
|
|
result := true;
|
|
end
|
|
else
|
|
begin
|
|
PWord(UpperCopy255(up{%H-}, p^.Name))^ := ord('=');
|
|
v := FindIniNameValue(section, @up, #0);
|
|
if p^.Value.Parser in ptMultiLineStringTypes then
|
|
begin
|
|
if v = #0 then // may be stored in a multi-line section body
|
|
begin
|
|
name := @up;
|
|
if Level <> 0 then
|
|
begin
|
|
name := UpperCopy255(name, SectionName);
|
|
name^ := '.';
|
|
inc(name);
|
|
end;
|
|
PWord(UpperCopy255(name, p^.Name))^ := ord(']');
|
|
nested := pointer(Ini);
|
|
if FindSectionFirstLine(nested, @up) then
|
|
begin
|
|
// multi-line text value has been stored in its own section
|
|
v := GetSectionContent(nested);
|
|
if p^.Prop^.SetValueText(Instance, v) then
|
|
result := true;
|
|
end;
|
|
end
|
|
else if p^.Prop^.SetValueText(Instance, v) then // single line text
|
|
result := true;
|
|
end
|
|
else if v <> #0 then
|
|
if (p^.OffsetSet <= 0) or // has a setter?
|
|
(rcfBoolean in p^.Value.Cache.Flags) or // simple value?
|
|
(p^.Value.Kind in (rkGetIntegerPropTypes + [rkEnumeration, rkFloat])) then
|
|
begin
|
|
if p^.Prop^.SetValueText(Instance, v) then // RTTI conversion
|
|
result := true;
|
|
end
|
|
else // e.g. rkVariant, rkSet, rkDynArray
|
|
begin
|
|
json := pointer(v); // convert complex values from JSON
|
|
GetDataFromJson(@PByteArray(Instance)[p^.OffsetSet], json,
|
|
nil, p^.Value, DocVariantOptions, true, nil);
|
|
if json <> nil then
|
|
result := true;
|
|
end;
|
|
end;
|
|
inc(p);
|
|
end;
|
|
end;
|
|
|
|
function TrimAndIsMultiLine(var U: RawUtf8): boolean;
|
|
var
|
|
L: PtrInt;
|
|
P: PUtf8Char absolute U;
|
|
begin
|
|
result := false;
|
|
L := length(U);
|
|
if L = 0 then
|
|
exit;
|
|
while P[L - 1] in [#13, #10] do
|
|
begin
|
|
dec(L);
|
|
if L = 0 then
|
|
begin
|
|
U := ''; // no meaningful text
|
|
exit;
|
|
end;
|
|
end;
|
|
if L <> length(U) then
|
|
SetLength(U, L); // trim right
|
|
if BufferLineLength(P, P + L) = L then // may use x86_64 SSE2 asm
|
|
exit; // no line feed
|
|
result := true; // there are line feeds within this text
|
|
U := TrimChar(U, [#13]); // normalize #13#10 into #10 as ObjectToIni()
|
|
end;
|
|
|
|
function ObjectToIni(const Instance: TObject; const SectionName: RawUtf8;
|
|
Options: TTextWriterWriteObjectOptions; Level: integer): RawUtf8;
|
|
var
|
|
W: TTextWriter;
|
|
tmp: TTextWriterStackBuffer;
|
|
nested: TRawUtf8DynArray;
|
|
i, nestedcount: integer;
|
|
r: TRttiCustom;
|
|
p: PRttiCustomProp;
|
|
n, s: RawUtf8;
|
|
begin
|
|
result := '';
|
|
if Instance = nil then
|
|
exit;
|
|
nestedcount := 0;
|
|
W := DefaultJsonWriter.CreateOwnedStream(tmp);
|
|
try
|
|
W.CustomOptions := W.CustomOptions + [twoTrimLeftEnumSets];
|
|
W.Add('[%]'#10, [SectionName]);
|
|
r := Rtti.RegisterClass(Instance);
|
|
p := pointer(r.Props.List);
|
|
for i := 1 to r.Props.Count do
|
|
begin
|
|
if p^.Prop <> nil then
|
|
if p^.Value.Kind = rkClass then
|
|
begin
|
|
if Level = 0 then
|
|
n := p^.Name
|
|
else
|
|
n := SectionName + '.' + p^.Name;
|
|
s := ObjectToIni(p^.Prop^.GetObjProp(Instance), n, Options, Level + 1);
|
|
if s <> '' then
|
|
AddRawUtf8(nested, nestedcount, s);
|
|
end
|
|
else if p^.Value.Kind = rkEnumeration then
|
|
begin
|
|
if woHumanReadableEnumSetAsComment in Options then
|
|
begin
|
|
p^.Value.Cache.EnumInfo^.GetEnumNameAll(
|
|
s, '; values=', {quoted=}false, #10, {uncamelcase=}true);
|
|
W.AddString(s);
|
|
end;
|
|
// AddValueJson() would have written "quotes"
|
|
W.AddString(p^.Name);
|
|
W.Add('=');
|
|
W.AddTrimLeftLowerCase(p^.Value.Cache.EnumInfo^.GetEnumNameOrd(
|
|
p^.Prop^.GetOrdProp(Instance)));
|
|
W.Add(#10);
|
|
end
|
|
else if p^.Value.Parser in ptMultiLineStringTypes then
|
|
begin
|
|
p^.Prop^.GetAsString(Instance, s);
|
|
if TrimAndIsMultiLine(s) then
|
|
begin
|
|
// store multi-line text values in their own section
|
|
if Level = 0 then
|
|
FormatUtf8('[%]'#10'%'#10#10, [p^.Name, s], n)
|
|
else
|
|
FormatUtf8('[%.%]'#10'%'#10#10, [SectionName, p^.Name, s], n);
|
|
AddRawUtf8(nested, nestedcount, n);
|
|
end
|
|
else
|
|
begin
|
|
W.AddString(p^.Name);
|
|
W.Add('=');
|
|
W.AddString(s); // single line text
|
|
W.Add(#10);
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
W.AddString(p^.Name);
|
|
W.Add('=');
|
|
p^.AddValueJson(W, Instance, // simple and complex types
|
|
Options - [woHumanReadableEnumSetAsComment], twOnSameLine);
|
|
W.Add(#10);
|
|
end;
|
|
inc(p);
|
|
end;
|
|
W.Add(#10);
|
|
for i := 0 to nestedcount - 1 do
|
|
W.AddString(nested[i]);
|
|
W.SetText(result);
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ ************ RawUtf8 String Values Interning and TRawUtf8List }
|
|
|
|
{ TRawUtf8Hashed }
|
|
|
|
procedure TRawUtf8Hashed.Init;
|
|
begin
|
|
Values.InitSpecific(TypeInfo(TRawUtf8DynArray), Value, ptRawUtf8,
|
|
@Count, false, InterningHasher);
|
|
end;
|
|
|
|
|
|
{ TRawUtf8InterningSlot }
|
|
|
|
procedure TRawUtf8InterningSlot.Init;
|
|
begin
|
|
fHash.Init;
|
|
end;
|
|
|
|
procedure TRawUtf8InterningSlot.Unique(var aResult: RawUtf8;
|
|
const aText: RawUtf8; aTextHash: cardinal);
|
|
var
|
|
i: PtrInt;
|
|
added: boolean;
|
|
begin
|
|
fSafe.ReadLock; // a TRWLightLock is faster here than an upgradable TRWLock
|
|
i := fHash.Values.Hasher.FindOrNewComp(aTextHash, @aText);
|
|
if i >= 0 then
|
|
begin
|
|
aResult := fHash.Value[i]; // return unified string instance
|
|
fSafe.ReadUnLock;
|
|
exit;
|
|
end;
|
|
fSafe.ReadUnLock;
|
|
fSafe.WriteLock; // need to be added within the write lock
|
|
i := fHash.Values.FindHashedForAdding(aText, added, aTextHash);
|
|
if added then
|
|
begin
|
|
fHash.Value[i] := aText; // copy new value to the pool
|
|
aResult := aText;
|
|
end
|
|
else
|
|
aResult := fHash.Value[i]; // was added in a background thread
|
|
fSafe.WriteUnLock;
|
|
end;
|
|
|
|
procedure TRawUtf8InterningSlot.UniqueFromBuffer(var aResult: RawUtf8;
|
|
aText: PUtf8Char; aTextLen: PtrInt; aTextHash: cardinal);
|
|
var
|
|
c: AnsiChar;
|
|
added: boolean;
|
|
i: PtrInt;
|
|
bak: TDynArraySortCompare;
|
|
begin
|
|
if not fSafe.TryReadLock then
|
|
begin
|
|
FastSetString(aResult, aText, aTextLen); // avoid waiting on contention
|
|
exit;
|
|
end;
|
|
c := aText[aTextLen];
|
|
aText[aTextLen] := #0; // input buffer may not be #0 terminated
|
|
i := fHash.Values.Hasher.FindOrNewComp(aTextHash, @aText, @SortDynArrayPUtf8Char);
|
|
if i >= 0 then
|
|
begin
|
|
aResult := fHash.Value[i]; // return unified string instance
|
|
fSafe.ReadUnLock;
|
|
aText[aTextLen] := c;
|
|
exit;
|
|
end;
|
|
fSafe.ReadUnLock;
|
|
fSafe.WriteLock; // need to be added
|
|
bak := fHash.Values.Hasher.Compare; // (RawUtf8,RawUtf8) -> (RawUtf8,PUtf8Char)
|
|
PDynArrayHasher(@fHash.Values.Hasher)^.fCompare := @SortDynArrayPUtf8Char;
|
|
i := fHash.Values.FindHashedForAdding(aText, added, aTextHash);
|
|
PDynArrayHasher(@fHash.Values.Hasher)^.fCompare := bak;
|
|
if added then
|
|
FastSetString(fHash.Value[i], aText, aTextLen); // new value to the pool
|
|
aResult := fHash.Value[i];
|
|
fSafe.WriteUnLock;
|
|
aText[aTextLen] := c;
|
|
end;
|
|
|
|
procedure TRawUtf8InterningSlot.UniqueText(var aText: RawUtf8; aTextHash: cardinal);
|
|
var
|
|
i: PtrInt;
|
|
added: boolean;
|
|
begin
|
|
fSafe.ReadLock;
|
|
i := fHash.Values.Hasher.FindOrNewComp(aTextHash, @aText);
|
|
if i >= 0 then
|
|
begin
|
|
aText := fHash.Value[i]; // return unified string instance
|
|
fSafe.ReadUnLock;
|
|
exit;
|
|
end;
|
|
fSafe.ReadUnLock;
|
|
fSafe.WriteLock; // need to be added
|
|
i := fHash.Values.FindHashedForAdding(aText, added, aTextHash);
|
|
if added then
|
|
fHash.Value[i] := aText // copy new value to the pool
|
|
else
|
|
aText := fHash.Value[i]; // was added in a background thread
|
|
fSafe.WriteUnLock;
|
|
end;
|
|
|
|
function TRawUtf8InterningSlot.Existing(const aText: RawUtf8; aTextHash: cardinal): pointer;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
result := nil;
|
|
fSafe.ReadLock;
|
|
i := fHash.Values.Hasher.FindOrNewComp(aTextHash, @aText);
|
|
if i >= 0 then
|
|
result := pointer(fHash.Value[i]); // return a pointer to unified string instance
|
|
fSafe.ReadUnLock;
|
|
end;
|
|
|
|
procedure TRawUtf8InterningSlot.Clear;
|
|
begin
|
|
fSafe.WriteLock;
|
|
try
|
|
fHash.Values.SetCount(0); // Values.Clear
|
|
fHash.Values.Hasher.ForceReHash;
|
|
finally
|
|
fSafe.WriteUnLock;
|
|
end;
|
|
end;
|
|
|
|
function TRawUtf8InterningSlot.Clean(aMaxRefCount: TStrCnt): integer;
|
|
var
|
|
i: integer;
|
|
s, d: PPtrUInt; // points to RawUtf8 values
|
|
begin
|
|
result := 0;
|
|
if fHash.Count = 0 then
|
|
exit;
|
|
fSafe.WriteLock;
|
|
try
|
|
if fHash.Count = 0 then
|
|
exit;
|
|
s := pointer(fHash.Value);
|
|
d := s;
|
|
for i := 1 to fHash.Count do
|
|
begin
|
|
if PStrCnt(PAnsiChar(s^) - _STRCNT)^ <= aMaxRefCount then
|
|
begin
|
|
{$ifdef FPC}
|
|
FastAssignNew(PRawUtf8(s)^);
|
|
{$else}
|
|
PRawUtf8(s)^ := '';
|
|
{$endif FPC}
|
|
inc(result);
|
|
end
|
|
else
|
|
begin
|
|
if s <> d then
|
|
begin
|
|
d^ := s^; // bypass COW assignments
|
|
s^ := 0; // avoid GPF
|
|
end;
|
|
inc(d);
|
|
end;
|
|
inc(s);
|
|
end;
|
|
if result > 0 then
|
|
begin
|
|
fHash.Values.SetCount((PtrUInt(d) - PtrUInt(fHash.Value)) div SizeOf(d^));
|
|
fHash.Values.ForceReHash;
|
|
end;
|
|
finally
|
|
fSafe.WriteUnLock;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TRawUtf8Interning }
|
|
|
|
constructor TRawUtf8Interning.Create(aHashTables: integer);
|
|
var
|
|
p: integer;
|
|
i: PtrInt;
|
|
begin
|
|
inherited Create; // may have been overriden
|
|
for p := 0 to 9 do
|
|
if aHashTables = 1 shl p then
|
|
begin
|
|
SetLength(fPool, aHashTables);
|
|
fPoolLast := aHashTables - 1;
|
|
for i := 0 to fPoolLast do
|
|
fPool[i].Init;
|
|
exit;
|
|
end;
|
|
raise ESynException.CreateUtf8('%.Create(%) not allowed: ' +
|
|
'should be a power of 2 <= 512', [self, aHashTables]);
|
|
end;
|
|
|
|
procedure TRawUtf8Interning.Clear;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
if self <> nil then
|
|
for i := 0 to fPoolLast do
|
|
fPool[i].Clear;
|
|
end;
|
|
|
|
function TRawUtf8Interning.Clean(aMaxRefCount: TStrCnt): integer;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
result := 0;
|
|
if self <> nil then
|
|
for i := 0 to fPoolLast do
|
|
inc(result, fPool[i].Clean(aMaxRefCount));
|
|
end;
|
|
|
|
function TRawUtf8Interning.Count: integer;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
result := 0;
|
|
if self <> nil then
|
|
for i := 0 to fPoolLast do
|
|
inc(result, fPool[i].Count);
|
|
end;
|
|
|
|
procedure TRawUtf8Interning.Unique(var aResult: RawUtf8; const aText: RawUtf8);
|
|
var
|
|
hash: cardinal;
|
|
begin
|
|
if aText = '' then
|
|
aResult := ''
|
|
else if self = nil then
|
|
aResult := aText
|
|
else
|
|
begin
|
|
// inlined fPool[].Values.HashElement
|
|
hash := InterningHasher(0, pointer(aText), length(aText));
|
|
fPool[hash and fPoolLast].Unique(aResult, aText, hash);
|
|
end;
|
|
end;
|
|
|
|
procedure TRawUtf8Interning.UniqueText(var aText: RawUtf8);
|
|
var
|
|
hash: cardinal;
|
|
begin
|
|
if (self <> nil) and
|
|
(aText <> '') then
|
|
begin
|
|
// inlined fPool[].Values.HashElement
|
|
hash := InterningHasher(0, pointer(aText), length(aText));
|
|
fPool[hash and fPoolLast].UniqueText(aText, hash);
|
|
end;
|
|
end;
|
|
|
|
function TRawUtf8Interning.Unique(const aText: RawUtf8): RawUtf8;
|
|
var
|
|
hash: cardinal;
|
|
begin
|
|
if aText = '' then
|
|
FastAssignNew(result)
|
|
else if self = nil then
|
|
result := aText
|
|
else
|
|
begin
|
|
// inlined fPool[].Values.HashElement
|
|
hash := InterningHasher(0, pointer(aText), length(aText));
|
|
fPool[hash and fPoolLast].Unique(result, aText, hash);
|
|
end;
|
|
end;
|
|
|
|
function TRawUtf8Interning.Existing(const aText: RawUtf8): pointer;
|
|
var
|
|
hash: cardinal;
|
|
begin
|
|
result := nil;
|
|
if self = nil then
|
|
exit;
|
|
hash := InterningHasher(0, pointer(aText), length(aText));
|
|
result := fPool[hash and fPoolLast].Existing(aText, hash);
|
|
end;
|
|
|
|
function TRawUtf8Interning.Unique(aText: PUtf8Char; aTextLen: PtrInt): RawUtf8;
|
|
begin
|
|
Unique(result, aText, aTextLen);
|
|
end;
|
|
|
|
procedure TRawUtf8Interning.Unique(var aResult: RawUtf8;
|
|
aText: PUtf8Char; aTextLen: PtrInt);
|
|
var
|
|
hash: cardinal;
|
|
begin
|
|
if (aText = nil) or
|
|
(aTextLen <= 0) then
|
|
FastAssignNew(aResult)
|
|
else if self = nil then
|
|
FastSetString(aResult, aText, aTextLen)
|
|
else
|
|
begin
|
|
// inlined fPool[].Values.HashElement
|
|
hash := InterningHasher(0, pointer(aText), aTextLen);
|
|
fPool[hash and fPoolLast].UniqueFromBuffer(aResult, aText, aTextLen, hash);
|
|
end;
|
|
end;
|
|
|
|
procedure TRawUtf8Interning.UniqueVariant(
|
|
var aResult: variant; const aText: RawUtf8);
|
|
begin
|
|
ClearVariantForString(aResult);
|
|
Unique(RawUtf8(TVarData(aResult).VAny), aText);
|
|
end;
|
|
|
|
procedure TRawUtf8Interning.UniqueVariantString(var aResult: variant;
|
|
const aText: string);
|
|
var
|
|
tmp: RawUtf8;
|
|
begin
|
|
StringToUtf8(aText, tmp);
|
|
UniqueVariant(aResult, tmp);
|
|
end;
|
|
|
|
procedure TRawUtf8Interning.UniqueVariant(var aResult: variant);
|
|
var
|
|
vd: TVarData absolute aResult;
|
|
vt: cardinal;
|
|
begin
|
|
vt := vd.VType;
|
|
if vt = varString then
|
|
UniqueText(RawUtf8(vd.VString))
|
|
else if vt = varVariantByRef then
|
|
UniqueVariant(PVariant(vd.VPointer)^)
|
|
else if vt = varStringByRef then
|
|
UniqueText(PRawUtf8(vd.VPointer)^);
|
|
end;
|
|
|
|
|
|
{ TRawUtf8List }
|
|
|
|
{$ifdef PUREMORMOT2}
|
|
constructor TRawUtf8List.Create;
|
|
begin
|
|
CreateEx([fCaseSensitive]);
|
|
end;
|
|
{$else}
|
|
constructor TRawUtf8List.Create;
|
|
begin
|
|
SetDefaultFlags;
|
|
CreateEx(fFlags + [fCaseSensitive]);
|
|
end;
|
|
|
|
constructor TRawUtf8List.Create(aOwnObjects, aNoDuplicate, aCaseSensitive: boolean);
|
|
begin
|
|
SetDefaultFlags;
|
|
if aOwnObjects then
|
|
include(fFlags, fObjectsOwned);
|
|
if aNoDuplicate then
|
|
include(fFlags, fNoDuplicate);
|
|
if aCaseSensitive then
|
|
include(fFlags, fCaseSensitive);
|
|
CreateEx(fFlags);
|
|
end;
|
|
|
|
procedure TRawUtf8List.SetDefaultFlags;
|
|
begin
|
|
end;
|
|
procedure TRawUtf8ListLocked.SetDefaultFlags;
|
|
begin
|
|
fFlags := [fThreadSafe];
|
|
end;
|
|
procedure TRawUtf8ListHashed.SetDefaultFlags;
|
|
begin
|
|
fFlags := [fNoDuplicate];
|
|
end;
|
|
procedure TRawUtf8ListHashedLocked.SetDefaultFlags;
|
|
begin
|
|
fFlags := [fNoDuplicate, fThreadSafe];
|
|
end;
|
|
{$endif PUREMORMOT2}
|
|
|
|
constructor TRawUtf8List.CreateEx(aFlags: TRawUtf8ListFlags);
|
|
begin
|
|
inherited Create; // may have been overriden
|
|
fNameValueSep := '=';
|
|
fFlags := aFlags;
|
|
fValues.InitSpecific(TypeInfo(TRawUtf8DynArray), fValue, ptRawUtf8, @fCount,
|
|
not (fCaseSensitive in aFlags));
|
|
end;
|
|
|
|
destructor TRawUtf8List.Destroy;
|
|
begin
|
|
SetCapacity(0);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TRawUtf8List.SetCaseSensitive(Value: boolean);
|
|
begin
|
|
if (self = nil) or
|
|
(fCaseSensitive in fFlags = Value) then
|
|
exit;
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteLock;
|
|
try
|
|
if Value then
|
|
include(fFlags, fCaseSensitive)
|
|
else
|
|
exclude(fFlags, fCaseSensitive);
|
|
fValues.Hasher.InitSpecific(@fValues, ptRawUtf8, not Value, nil);
|
|
Changed;
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteUnLock;
|
|
end;
|
|
end;
|
|
|
|
procedure TRawUtf8List.SetCapacity(const capa: PtrInt);
|
|
begin
|
|
if self <> nil then
|
|
begin
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteLock;
|
|
try
|
|
if capa <= 0 then
|
|
begin
|
|
// clear
|
|
if fObjects <> nil then
|
|
begin
|
|
if fObjectsOwned in fFlags then
|
|
RawObjectsClear(pointer(fObjects), fCount);
|
|
fObjects := nil;
|
|
end;
|
|
fValues.Clear;
|
|
if fNoDuplicate in fFlags then
|
|
fValues.ForceReHash;
|
|
Changed;
|
|
end
|
|
else
|
|
begin
|
|
// resize
|
|
if capa < fCount then
|
|
begin
|
|
// resize down
|
|
if fObjects <> nil then
|
|
begin
|
|
if fObjectsOwned in fFlags then
|
|
RawObjectsClear(@fObjects[capa], fCount - capa - 1);
|
|
SetLength(fObjects, capa);
|
|
end;
|
|
fValues.Count := capa;
|
|
if fNoDuplicate in fFlags then
|
|
fValues.ForceReHash;
|
|
Changed;
|
|
end;
|
|
if capa > length(fValue) then
|
|
begin
|
|
// resize up
|
|
SetLength(fValue, capa);
|
|
if fObjects <> nil then
|
|
SetLength(fObjects, capa);
|
|
end;
|
|
end;
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteUnLock;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TRawUtf8List.Add(const aText: RawUtf8; aRaiseExceptionIfExisting: boolean): PtrInt;
|
|
begin
|
|
result := AddObject(aText, nil, aRaiseExceptionIfExisting);
|
|
end;
|
|
|
|
function TRawUtf8List.AddObject(const aText: RawUtf8; aObject: TObject;
|
|
aRaiseExceptionIfExisting: boolean; aFreeAndReturnExistingObject: PPointer;
|
|
aReplaceExistingObject: boolean): PtrInt;
|
|
var
|
|
added: boolean;
|
|
obj: TObject;
|
|
begin
|
|
result := -1;
|
|
if self = nil then
|
|
exit;
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteLock;
|
|
try
|
|
if fNoDuplicate in fFlags then
|
|
begin
|
|
result := fValues.FindHashedForAdding(aText, added, {noadd=}true);
|
|
if not added then
|
|
begin
|
|
obj := GetObject(result);
|
|
if (obj = aObject) and
|
|
(obj <> nil) then
|
|
exit; // found identical aText/aObject -> behave as if added
|
|
if aFreeAndReturnExistingObject <> nil then
|
|
begin
|
|
aObject.Free;
|
|
aFreeAndReturnExistingObject^ := obj;
|
|
end;
|
|
if aRaiseExceptionIfExisting then
|
|
raise ESynException.CreateUtf8('%.Add duplicate [%]', [self, aText]);
|
|
if aReplaceExistingObject then
|
|
begin
|
|
if obj = nil then
|
|
raise ESynException.CreateUtf8(
|
|
'%.AddOrReplaceObject with no object at [%]', [self, aText]);
|
|
if fObjectsOwned in fFlags then
|
|
FreeAndNil(fObjects[result]);
|
|
fObjects[result] := aObject;
|
|
end
|
|
else
|
|
result := -1;
|
|
exit;
|
|
end;
|
|
end;
|
|
result := fValues.Add(aText);
|
|
if (fObjects <> nil) or
|
|
(aObject <> nil) then
|
|
begin
|
|
if result >= length(fObjects) then
|
|
SetLength(fObjects, length(fValue)); // same capacity
|
|
if aObject <> nil then
|
|
fObjects[result] := aObject;
|
|
end;
|
|
if Assigned(fOnChange) then
|
|
Changed;
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteUnLock;
|
|
end;
|
|
end;
|
|
|
|
function TRawUtf8List.AddOrReplaceObject(const aText: RawUtf8; aObject: TObject): PtrInt;
|
|
begin
|
|
result := AddObject(aText, aObject, {raiseexisting=}false, nil, {replace=}true);
|
|
end;
|
|
|
|
procedure TRawUtf8List.AddObjectUnique(const aText: RawUtf8;
|
|
aObjectToAddOrFree: PPointer);
|
|
begin
|
|
if fNoDuplicate in fFlags then
|
|
AddObject(aText, aObjectToAddOrFree^, {raiseexc=}false,
|
|
{freeandreturnexisting=}aObjectToAddOrFree);
|
|
end;
|
|
|
|
procedure TRawUtf8List.AddRawUtf8List(List: TRawUtf8List);
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
if List <> nil then
|
|
begin
|
|
BeginUpdate; // includes Safe.Lock
|
|
try
|
|
for i := 0 to List.fCount - 1 do
|
|
AddObject(List.fValue[i], List.GetObject(i));
|
|
finally
|
|
EndUpdate;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TRawUtf8List.BeginUpdate;
|
|
begin
|
|
if InterLockedIncrement(fOnChangeLevel) > 1 then
|
|
exit;
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteLock;
|
|
fOnChangeBackupForBeginUpdate := fOnChange;
|
|
fOnChange := OnChangeHidden;
|
|
exclude(fFlags, fOnChangeTrigerred);
|
|
end;
|
|
|
|
procedure TRawUtf8List.EndUpdate;
|
|
begin
|
|
if (fOnChangeLevel <= 0) or
|
|
(InterLockedDecrement(fOnChangeLevel) > 0) then
|
|
exit; // allows nested BeginUpdate..EndUpdate calls
|
|
fOnChange := fOnChangeBackupForBeginUpdate;
|
|
if (fOnChangeTrigerred in fFlags) and
|
|
Assigned(fOnChange) then
|
|
Changed;
|
|
exclude(fFlags, fOnChangeTrigerred);
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteUnLock;
|
|
end;
|
|
|
|
procedure TRawUtf8List.Changed;
|
|
begin
|
|
if Assigned(fOnChange) then
|
|
try
|
|
fOnChange(self);
|
|
except // ignore any exception in user code (may not trigger fSafe.UnLock)
|
|
end;
|
|
end;
|
|
|
|
procedure TRawUtf8List.Clear;
|
|
begin
|
|
SetCapacity(0); // will also call Changed
|
|
end;
|
|
|
|
procedure TRawUtf8List.InternalDelete(Index: PtrInt);
|
|
begin
|
|
// caller ensured Index is correct
|
|
fValues.Delete(Index); // includes dec(fCount)
|
|
if PtrUInt(Index) < PtrUInt(length(fObjects)) then
|
|
begin
|
|
if fObjectsOwned in fFlags then
|
|
fObjects[Index].Free;
|
|
if fCount > Index then
|
|
MoveFast(fObjects[Index + 1], fObjects[Index],
|
|
(fCount - Index) * SizeOf(pointer));
|
|
fObjects[fCount] := nil;
|
|
end;
|
|
if Assigned(fOnChange) then
|
|
Changed;
|
|
end;
|
|
|
|
procedure TRawUtf8List.Delete(Index: PtrInt);
|
|
begin
|
|
if (self <> nil) and
|
|
(PtrUInt(Index) < PtrUInt(fCount)) then
|
|
if fNoDuplicate in fFlags then // force update the hash table
|
|
Delete(fValue[Index])
|
|
else
|
|
InternalDelete(Index);
|
|
end;
|
|
|
|
function TRawUtf8List.Delete(const aText: RawUtf8): PtrInt;
|
|
begin
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteLock;
|
|
try
|
|
if fNoDuplicate in fFlags then
|
|
result := fValues.FindHashedAndDelete(aText, nil, {nodelete=}true)
|
|
else
|
|
result := FindRawUtf8(pointer(fValue), aText, fCount, fCaseSensitive in fFlags);
|
|
if result >= 0 then
|
|
InternalDelete(result);
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteUnLock;
|
|
end;
|
|
end;
|
|
|
|
function TRawUtf8List.DeleteFromName(const Name: RawUtf8): PtrInt;
|
|
begin
|
|
result := -1;
|
|
if fThreadSafe in fFlags then
|
|
fSafe.ReadWriteLock;
|
|
try
|
|
result := IndexOfName(Name);
|
|
if result >= 0 then
|
|
begin
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteLock;
|
|
Delete(result);
|
|
end;
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
begin
|
|
if result >= 0 then
|
|
fSafe.WriteUnlock;
|
|
fSafe.ReadWriteUnLock;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TRawUtf8List.Exists(const aText: RawUtf8): boolean;
|
|
begin
|
|
if self <> nil then
|
|
if fThreadSafe in fFlags then
|
|
begin
|
|
fSafe.ReadOnlyLock;
|
|
try
|
|
result := IndexOf(aText) >= 0;
|
|
finally
|
|
fSafe.ReadOnlyUnLock;
|
|
end;
|
|
end
|
|
else
|
|
result := IndexOf(aText) >= 0
|
|
else
|
|
result := false;
|
|
end;
|
|
|
|
function TRawUtf8List.IndexOf(const aText: RawUtf8): PtrInt;
|
|
begin
|
|
if self <> nil then
|
|
begin
|
|
if fNoDuplicate in fFlags then
|
|
result := fValues.FindHashed(aText)
|
|
else
|
|
result := FindRawUtf8(
|
|
pointer(fValue), aText, fCount, fCaseSensitive in fFlags);
|
|
end
|
|
else
|
|
result := -1;
|
|
end;
|
|
|
|
function TRawUtf8List.Get(Index: PtrInt): RawUtf8;
|
|
begin
|
|
if (self = nil) or
|
|
(PtrUInt(Index) >= PtrUInt(fCount)) then
|
|
result := ''
|
|
else
|
|
result := fValue[Index];
|
|
end;
|
|
|
|
function TRawUtf8List.GetS(Index: PtrInt): string;
|
|
begin
|
|
if (self = nil) or
|
|
(PtrUInt(Index) >= PtrUInt(fCount)) then
|
|
result := ''
|
|
else
|
|
Utf8ToStringVar(fValue[Index], result);
|
|
end;
|
|
|
|
function TRawUtf8List.GetCapacity: PtrInt;
|
|
begin
|
|
if self = nil then
|
|
result := 0
|
|
else
|
|
result := length(fValue);
|
|
end;
|
|
|
|
function TRawUtf8List.GetCount: PtrInt;
|
|
begin
|
|
if self = nil then
|
|
result := 0
|
|
else
|
|
result := fCount;
|
|
end;
|
|
|
|
function TRawUtf8List.GetTextPtr: PPUtf8CharArray;
|
|
begin
|
|
result := pointer(self);
|
|
if self <> nil then
|
|
result := pointer(fValue);
|
|
end;
|
|
|
|
function TRawUtf8List.GetObjectPtr: PPointerArray;
|
|
begin
|
|
result := pointer(self);
|
|
if self <> nil then
|
|
result := pointer(fObjects);
|
|
end;
|
|
|
|
function TRawUtf8List.GetName(Index: PtrInt): RawUtf8;
|
|
begin
|
|
result := Get(Index);
|
|
if result = '' then
|
|
exit;
|
|
Index := PosExChar(NameValueSep, result);
|
|
if Index = 0 then
|
|
result := ''
|
|
else
|
|
SetLength(result, Index - 1);
|
|
end;
|
|
|
|
function TRawUtf8List.GetObject(Index: PtrInt): pointer;
|
|
begin
|
|
if (self <> nil) and
|
|
(fObjects <> nil) and
|
|
(PtrUInt(Index) < PtrUInt(fCount)) then
|
|
result := fObjects[Index]
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
function TRawUtf8List.GetObjectFrom(const aText: RawUtf8): pointer;
|
|
var
|
|
ndx: PtrUInt;
|
|
begin
|
|
result := nil;
|
|
if (self <> nil) and
|
|
(fObjects <> nil) then
|
|
begin
|
|
if fThreadSafe in fFlags then
|
|
fSafe.ReadOnlyLock;
|
|
try
|
|
ndx := IndexOf(aText);
|
|
if ndx < PtrUInt(fCount) then
|
|
result := fObjects[ndx];
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
fSafe.ReadOnlyUnLock;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TRawUtf8List.GetText(const Delimiter: RawUtf8): RawUtf8;
|
|
var
|
|
DelimLen, i, Len: PtrInt;
|
|
P: PUtf8Char;
|
|
begin
|
|
result := '';
|
|
if (self = nil) or
|
|
(fCount = 0) then
|
|
exit;
|
|
if fThreadSafe in fFlags then
|
|
fSafe.ReadOnlyLock;
|
|
try
|
|
DelimLen := length(Delimiter);
|
|
Len := DelimLen * (fCount - 1);
|
|
for i := 0 to fCount - 1 do
|
|
inc(Len, length(fValue[i]));
|
|
FastSetString(result, Len);
|
|
P := pointer(result);
|
|
i := 0;
|
|
repeat
|
|
Len := length(fValue[i]);
|
|
if Len > 0 then
|
|
begin
|
|
MoveFast(pointer(fValue[i])^, P^, Len);
|
|
inc(P, Len);
|
|
end;
|
|
inc(i);
|
|
if i >= fCount then
|
|
Break;
|
|
if DelimLen > 0 then
|
|
begin
|
|
MoveByOne(pointer(Delimiter), P, DelimLen);
|
|
inc(P, DelimLen);
|
|
end;
|
|
until false;
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
fSafe.ReadOnlyUnLock;
|
|
end;
|
|
end;
|
|
|
|
procedure TRawUtf8List.SaveToStream(Dest: TStream; const Delimiter: RawUtf8);
|
|
var
|
|
W: TTextWriter;
|
|
i: PtrInt;
|
|
temp: TTextWriterStackBuffer;
|
|
begin
|
|
if (self = nil) or
|
|
(fCount = 0) then
|
|
exit;
|
|
if fThreadSafe in fFlags then
|
|
fSafe.ReadOnlyLock;
|
|
try
|
|
W := TTextWriter.Create(Dest, @temp, SizeOf(temp));
|
|
try
|
|
i := 0;
|
|
repeat
|
|
W.AddString(fValue[i]);
|
|
inc(i);
|
|
if i >= fCount then
|
|
Break;
|
|
W.AddString(Delimiter);
|
|
until false;
|
|
W.FlushFinal;
|
|
finally
|
|
W.Free;
|
|
end;
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
fSafe.ReadOnlyUnLock;
|
|
end;
|
|
end;
|
|
|
|
procedure TRawUtf8List.SaveToFile(
|
|
const FileName: TFileName; const Delimiter: RawUtf8);
|
|
var
|
|
FS: TStream;
|
|
begin
|
|
FS := TFileStreamEx.Create(FileName, fmCreate);
|
|
try
|
|
SaveToStream(FS, Delimiter);
|
|
finally
|
|
FS.Free;
|
|
end;
|
|
end;
|
|
|
|
function TRawUtf8List.GetTextCRLF: RawUtf8;
|
|
begin
|
|
result := GetText;
|
|
end;
|
|
|
|
function TRawUtf8List.GetValue(const Name: RawUtf8): RawUtf8;
|
|
begin
|
|
if fThreadSafe in fFlags then
|
|
fSafe.ReadOnlyLock;
|
|
try
|
|
result := GetValueAt(IndexOfName(Name));
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
fSafe.ReadOnlyUnLock;
|
|
end;
|
|
end;
|
|
|
|
function TRawUtf8List.GetValueAt(Index: PtrInt): RawUtf8;
|
|
begin
|
|
result := Get(Index);
|
|
if result = '' then
|
|
exit;
|
|
Index := PosExChar(NameValueSep, result);
|
|
if Index = 0 then
|
|
result := ''
|
|
else
|
|
TrimChars(result, Index, 0);
|
|
end;
|
|
|
|
function TRawUtf8List.EqualValueAt(Index: PtrInt; const aText: RawUtf8): boolean;
|
|
begin
|
|
result := (self <>nil) and
|
|
(PtrUInt(Index) < PtrUInt(fCount)) and
|
|
(fValue[Index] = aText);
|
|
end;
|
|
|
|
function TRawUtf8List.IndexOfName(const Name: RawUtf8): PtrInt;
|
|
var
|
|
UpperName: array[byte] of AnsiChar;
|
|
table: PNormTable;
|
|
begin
|
|
if self <> nil then
|
|
begin
|
|
PWord(UpperCopy255(UpperName{%H-}, Name))^ := ord(NameValueSep);
|
|
table := @NormToUpperAnsi7;
|
|
for result := 0 to fCount - 1 do
|
|
if IdemPChar(Pointer(fValue[result]), UpperName, table) then
|
|
exit;
|
|
end;
|
|
result := -1;
|
|
end;
|
|
|
|
function TRawUtf8List.IndexOfObject(aObject: TObject): PtrInt;
|
|
begin
|
|
if (self <> nil) and
|
|
(fObjects <> nil) then
|
|
begin
|
|
if fThreadSafe in fFlags then
|
|
fSafe.ReadOnlyLock;
|
|
try
|
|
result := PtrUIntScanIndex(pointer(fObjects), fCount, PtrUInt(aObject));
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
fSafe.ReadOnlyUnLock;
|
|
end
|
|
end
|
|
else
|
|
result := -1;
|
|
end;
|
|
|
|
function TRawUtf8List.Contains(const aText: RawUtf8; aFirstIndex: integer): PtrInt;
|
|
var
|
|
i: PtrInt; // use a temp variable to make oldest Delphi happy :(
|
|
begin
|
|
result := -1;
|
|
if self = nil then
|
|
exit;
|
|
if fThreadSafe in fFlags then
|
|
fSafe.ReadOnlyLock;
|
|
try
|
|
for i := aFirstIndex to fCount - 1 do
|
|
if PosEx(aText, fValue[i]) > 0 then
|
|
begin
|
|
result := i;
|
|
exit;
|
|
end;
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
fSafe.ReadOnlyUnLock;
|
|
end;
|
|
end;
|
|
|
|
procedure TRawUtf8List.OnChangeHidden(Sender: TObject);
|
|
begin
|
|
if self <> nil then
|
|
include(fFlags, fOnChangeTrigerred);
|
|
end;
|
|
|
|
procedure TRawUtf8List.Put(Index: PtrInt; const Value: RawUtf8);
|
|
begin
|
|
if (self <> nil) and
|
|
(PtrUInt(Index) < PtrUInt(fCount)) then
|
|
begin
|
|
fValue[Index] := Value;
|
|
if Assigned(fOnChange) then
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TRawUtf8List.PutS(Index: PtrInt; const Value: string);
|
|
begin
|
|
Put(Index, StringToUtf8(Value));
|
|
end;
|
|
|
|
procedure TRawUtf8List.PutObject(Index: PtrInt; Value: pointer);
|
|
begin
|
|
if (self <> nil) and
|
|
(PtrUInt(Index) < PtrUInt(fCount)) then
|
|
begin
|
|
if fObjects = nil then
|
|
SetLength(fObjects, Length(fValue));
|
|
fObjects[Index] := Value;
|
|
if Assigned(fOnChange) then
|
|
Changed;
|
|
end;
|
|
end;
|
|
|
|
procedure TRawUtf8List.SetText(const aText: RawUtf8; const Delimiter: RawUtf8);
|
|
begin
|
|
SetTextPtr(pointer(aText), PUtf8Char(pointer(aText)) + length(aText), Delimiter);
|
|
end;
|
|
|
|
procedure TRawUtf8List.LoadFromFile(const FileName: TFileName);
|
|
begin
|
|
SetText(RawUtf8FromFile(FileName), #13#10); // RawUtf8FromFile() detects BOM
|
|
end;
|
|
|
|
procedure TRawUtf8List.SetTextPtr(P, PEnd: PUtf8Char; const Delimiter: RawUtf8);
|
|
var
|
|
DelimLen: PtrInt;
|
|
DelimFirst: AnsiChar;
|
|
PBeg, DelimNext: PUtf8Char;
|
|
Line: RawUtf8;
|
|
begin
|
|
DelimLen := length(Delimiter);
|
|
BeginUpdate; // also makes fSafe.Lock
|
|
try
|
|
Clear;
|
|
if (P <> nil) and
|
|
(DelimLen > 0) and
|
|
(P < PEnd) then
|
|
begin
|
|
DelimFirst := Delimiter[1];
|
|
DelimNext := PUtf8Char(pointer(Delimiter)) + 1;
|
|
repeat
|
|
PBeg := P;
|
|
while P < PEnd do
|
|
begin
|
|
if (P^ = DelimFirst) and
|
|
CompareMemSmall(P + 1, DelimNext, DelimLen - 1) then
|
|
break;
|
|
inc(P);
|
|
end;
|
|
FastSetString(Line, PBeg, P - PBeg);
|
|
AddObject(Line, nil);
|
|
if P >= PEnd then
|
|
break;
|
|
inc(P, DelimLen);
|
|
until P >= PEnd;
|
|
end;
|
|
finally
|
|
EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
procedure TRawUtf8List.SetTextCRLF(const Value: RawUtf8);
|
|
begin
|
|
SetText(Value, #13#10);
|
|
end;
|
|
|
|
procedure TRawUtf8List.SetFrom(const aText: TRawUtf8DynArray;
|
|
const aObject: TObjectDynArray);
|
|
var
|
|
n: integer;
|
|
begin
|
|
BeginUpdate; // also makes fSafe.Lock
|
|
try
|
|
Clear;
|
|
n := length(aText);
|
|
if n = 0 then
|
|
exit;
|
|
SetCapacity(n);
|
|
fCount := n;
|
|
fValue := aText;
|
|
fObjects := aObject;
|
|
if fNoDuplicate in fFlags then
|
|
fValues.ForceReHash;
|
|
finally
|
|
EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
procedure TRawUtf8List.SetValue(const Name, Value: RawUtf8);
|
|
var
|
|
i: PtrInt;
|
|
txt: RawUtf8;
|
|
begin
|
|
txt := Name + RawUtf8(NameValueSep) + Value;
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteLock;
|
|
try
|
|
i := IndexOfName(Name);
|
|
if i < 0 then
|
|
AddObject(txt, nil)
|
|
else if fValue[i] <> txt then
|
|
begin
|
|
fValue[i] := txt;
|
|
if fNoDuplicate in fFlags then
|
|
fValues.Hasher.ForceReHash; // invalidate internal hash table
|
|
Changed;
|
|
end;
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteUnLock;
|
|
end;
|
|
end;
|
|
|
|
function TRawUtf8List.GetCaseSensitive: boolean;
|
|
begin
|
|
result := (self <> nil) and
|
|
(fCaseSensitive in fFlags);
|
|
end;
|
|
|
|
function TRawUtf8List.GetNoDuplicate: boolean;
|
|
begin
|
|
result := (self <> nil) and
|
|
(fNoDuplicate in fFlags);
|
|
end;
|
|
|
|
function TRawUtf8List.UpdateValue(const Name: RawUtf8; var Value: RawUtf8;
|
|
ThenDelete: boolean): boolean;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
result := false;
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteLock;
|
|
try
|
|
i := IndexOfName(Name);
|
|
if i >= 0 then
|
|
begin
|
|
Value := GetValueAt(i); // copy value
|
|
if ThenDelete then
|
|
Delete(i); // optionally delete
|
|
result := true;
|
|
end;
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteUnLock;
|
|
end;
|
|
end;
|
|
|
|
function TRawUtf8List.PopFirst(out aText: RawUtf8; aObject: PObject): boolean;
|
|
begin
|
|
result := false;
|
|
if fCount = 0 then
|
|
exit;
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteLock;
|
|
try
|
|
if fCount > 0 then
|
|
begin
|
|
aText := fValue[0];
|
|
if aObject <> nil then
|
|
if fObjects <> nil then
|
|
aObject^ := fObjects[0]
|
|
else
|
|
aObject^ := nil;
|
|
Delete(0);
|
|
result := true;
|
|
end;
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteUnLock;
|
|
end;
|
|
end;
|
|
|
|
function TRawUtf8List.PopLast(out aText: RawUtf8; aObject: PObject): boolean;
|
|
var
|
|
last: PtrInt;
|
|
begin
|
|
result := false;
|
|
if fCount = 0 then
|
|
exit;
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteLock;
|
|
try
|
|
last := fCount - 1;
|
|
if last >= 0 then
|
|
begin
|
|
aText := fValue[last];
|
|
if aObject <> nil then
|
|
if fObjects <> nil then
|
|
aObject^ := fObjects[last]
|
|
else
|
|
aObject^ := nil;
|
|
Delete(last);
|
|
result := true;
|
|
end;
|
|
finally
|
|
if fThreadSafe in fFlags then
|
|
fSafe.WriteUnLock;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ ********** Efficient RTTI Values Binary Serialization and Comparison }
|
|
|
|
// per-type efficient binary serialization
|
|
|
|
function _BS_Ord(Data: pointer; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
|
|
begin
|
|
result := ORDTYPE_SIZE[Info^.RttiOrd];
|
|
Dest.Write(Data, result);
|
|
end;
|
|
|
|
function _BL_Ord(Data: pointer; var Source: TFastReader; Info: PRttiInfo): PtrInt;
|
|
begin
|
|
result := ORDTYPE_SIZE[Info^.RttiOrd];
|
|
Source.Copy(Data, result);
|
|
end;
|
|
|
|
function _BS_Float(Data: pointer; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
|
|
begin
|
|
result := FLOATTYPE_SIZE[Info^.RttiFloat];
|
|
Dest.Write(Data, result);
|
|
end;
|
|
|
|
function _BL_Float(Data: pointer; var Source: TFastReader; Info: PRttiInfo): PtrInt;
|
|
begin
|
|
result := FLOATTYPE_SIZE[Info^.RttiFloat];
|
|
Source.Copy(Data, result);
|
|
end;
|
|
|
|
function _BS_64(Data: PInt64; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
|
|
begin
|
|
{$ifdef CPU32}
|
|
Dest.Write8(Data);
|
|
{$else}
|
|
Dest.WriteI64(Data^);
|
|
{$endif CPU32}
|
|
result := 8;
|
|
end;
|
|
|
|
function _BL_64(Data: PQWord; var Source: TFastReader; Info: PRttiInfo): PtrInt;
|
|
begin
|
|
Data^ := Source.Next8;
|
|
result := 8;
|
|
end;
|
|
|
|
function _BS_String(Data: PRawByteString; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
|
|
begin
|
|
Dest.WriteVar(pointer(Data^), length(Data^));
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function _BL_LString(Data: PRawByteString; var Source: TFastReader; Info: PRttiInfo): PtrInt;
|
|
begin
|
|
with Source.VarBlob do
|
|
{$ifdef HASCODEPAGE}
|
|
FastSetStringCP(Data^, Ptr, Len, Info^.AnsiStringCodePageStored);
|
|
{$else}
|
|
SetString(Data^, Ptr, Len);
|
|
{$endif HASCODEPAGE}
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
{$ifdef HASVARUSTRING}
|
|
|
|
function _BS_UString(Data: PUnicodeString; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
|
|
begin
|
|
Dest.WriteVar(pointer(Data^), length(Data^) * 2);
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function _BL_UString(Data: PUnicodeString; var Source: TFastReader; Info: PRttiInfo): PtrInt;
|
|
begin
|
|
with Source.VarBlob do
|
|
SetString(Data^, PWideChar(Ptr), Len shr 1); // length in bytes was stored
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
{$endif HASVARUSTRING}
|
|
|
|
function _BS_WString(Data: PWideString; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
|
|
begin
|
|
Dest.WriteVar(pointer(Data^), length(Data^) * 2);
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function _BL_WString(Data: PWideString; var Source: TFastReader; Info: PRttiInfo): PtrInt;
|
|
begin
|
|
with Source.VarBlob do
|
|
SetString(Data^, PWideChar(Ptr), Len shr 1); // length in bytes was stored
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
// efficient branchless comparison of every TRttiOrd/TRttiFloat raw value
|
|
|
|
function _BC_SByte(A, B: PShortInt; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := ord(A^ > B^) - ord(A^ < B^);
|
|
result := 1;
|
|
end;
|
|
|
|
function _BC_UByte(A, B: PByte; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := ord(A^ > B^) - ord(A^ < B^);
|
|
result := 1;
|
|
end;
|
|
|
|
function _BC_SWord(A, B: PSmallInt; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := ord(A^ > B^) - ord(A^ < B^);
|
|
result := 2;
|
|
end;
|
|
|
|
function _BC_UWord(A, B: PWord; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := ord(A^ > B^) - ord(A^ < B^);
|
|
result := 2;
|
|
end;
|
|
|
|
function _BC_SLong(A, B: PInteger; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := ord(A^ > B^) - ord(A^ < B^);
|
|
result := 4;
|
|
end;
|
|
|
|
function _BC_ULong(A, B: PCardinal; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := ord(A^ > B^) - ord(A^ < B^);
|
|
result := 4;
|
|
end;
|
|
|
|
function _BC_SQWord(A, B: PInt64; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := ord(A^ > B^) - ord(A^ < B^);
|
|
result := 8;
|
|
end;
|
|
|
|
function _BC_UQWord(A, B: PQWord; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := ord(A^ > B^) - ord(A^ < B^);
|
|
result := 8;
|
|
end;
|
|
|
|
function _BC_Ord(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
result := RTTI_ORD_COMPARE[Info^.RttiOrd](A, B, Info, Compared);
|
|
end;
|
|
|
|
function _BC_Single(A, B: PSingle; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := ord(A^ > B^) - ord(A^ < B^);
|
|
result := SizeOf(single);
|
|
end;
|
|
|
|
function _BC_Double(A, B: PDouble; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := ord(A^ > B^) - ord(A^ < B^);
|
|
result := SizeOf(double);
|
|
end;
|
|
|
|
function _BC_Extended(A, B: PExtended; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := ord(A^ > B^) - ord(A^ < B^);
|
|
result := SizeOf(extended);
|
|
end;
|
|
|
|
function _BC_Float(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
result := RTTI_FLOAT_COMPARE[Info^.RttiFloat](A, B, Info, Compared);
|
|
end;
|
|
|
|
function _BC_64(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
if Info^.IsQWord then
|
|
Compared := ord(PQWord(A)^ > PQWord(B)^) - ord(PQWord(A)^ < PQWord(B)^)
|
|
else
|
|
Compared := ord(PInt64(A)^ > PInt64(B)^) - ord(PInt64(A)^ < PInt64(B)^);
|
|
result := 8;
|
|
end;
|
|
|
|
function _BC_LString(A, B: PRawByteString; Info: PRttiInfo;
|
|
out Compared: integer): PtrInt;
|
|
begin
|
|
// StrComp() would fail for RawByteString
|
|
{$ifdef CPUINTEL}
|
|
compared := SortDynArrayAnsiString(A^, B^); // optimized asm using length()
|
|
{$else}
|
|
compared := SortDynArrayRawByteString(A^, B^);
|
|
{$endif CPUINTEL}
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function _BC_WString(A, B: PPWideChar; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
compared := StrCompW(A^, B^);
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function _BCI_LString(A, B: PPUtf8Char; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
compared := StrIComp(A^, B^);
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function _BCI_WString(A, B: PPWideChar; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
compared := AnsiICompW(A^, B^);
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function DelphiType(Info: PRttiInfo): integer;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
begin
|
|
// compatible with legacy TDynArray.SaveTo() format
|
|
if Info = nil then
|
|
result := 0
|
|
else
|
|
{$ifdef FPC}
|
|
result := ord(FPCTODELPHI[Info^.Kind]);
|
|
{$else}
|
|
result := ord(Info^.Kind);
|
|
{$endif FPC}
|
|
end;
|
|
|
|
procedure DynArraySave(Data: PAnsiChar; ExternalCount: PInteger;
|
|
Dest: TBufferWriter; Info: PRttiInfo);
|
|
var
|
|
n, itemsize: PtrInt;
|
|
sav: TRttiBinarySave;
|
|
label
|
|
raw;
|
|
begin
|
|
Info := Info^.DynArrayItemType(itemsize);
|
|
Dest.Write1(0); // warning: store itemsize=0 (mORMot 1 ignores it anyway)
|
|
Dest.Write1(DelphiType(Info));
|
|
Data := PPointer(Data)^; // de-reference pointer to array data
|
|
if Data = nil then
|
|
Dest.Write1(0) // store dynamic array count of 0
|
|
else
|
|
begin
|
|
if ExternalCount <> nil then
|
|
n := ExternalCount^ // e.g. from TDynArray with external count
|
|
else
|
|
n := PDALen(Data - _DALEN)^ + _DAOFF;
|
|
Dest.WriteVarUInt32(n);
|
|
Dest.Write4(0); // warning: we don't store any Hash32 checksum any more
|
|
if Info = nil then
|
|
raw: Dest.Write(Data, itemsize * n)
|
|
else
|
|
begin
|
|
sav := RTTI_BINARYSAVE[Info^.Kind];
|
|
if Assigned(sav) then // paranoid check
|
|
repeat
|
|
inc(Data, sav(Data, Dest, Info));
|
|
dec(n);
|
|
until n = 0
|
|
else
|
|
goto raw;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function _BS_DynArray(Data: PAnsiChar; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
|
|
begin
|
|
DynArraySave(Data, nil, Dest, Info);
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function DynArrayLoad(var Value; Source: PAnsiChar; TypeInfo: PRttiInfo;
|
|
TryCustomVariants: PDocVariantOptions; SourceMax: PAnsiChar): PAnsiChar;
|
|
begin
|
|
{$ifndef PUREMORMOT2}
|
|
if SourceMax = nil then
|
|
// mORMot 1 unsafe backward compatible: assume fake 100MB Source input
|
|
SourceMax := Source + 100 shl 20;
|
|
{$endif PUREMORMOT2}
|
|
result := BinaryLoad(
|
|
@Value, source, TypeInfo, nil, SourceMax, [rkDynArray], TryCustomVariants);
|
|
end;
|
|
|
|
function DynArraySave(var Value; TypeInfo: PRttiInfo): RawByteString;
|
|
begin
|
|
result := BinarySave(@Value, TypeInfo, [rkDynArray]);
|
|
end;
|
|
|
|
function DynArrayLoadHeader(var Source: TFastReader;
|
|
ArrayInfo, ItemInfo: PRttiInfo): integer;
|
|
begin
|
|
Source.VarNextInt; // ignore stored itemsize (0 stored now)
|
|
if Source.NextByte <> DelphiType(ItemInfo) then
|
|
Source.ErrorData('RTTI_BINARYLOAD[rkDynArray] failed for %', [ArrayInfo.RawName]);
|
|
result := Source.VarUInt32;
|
|
if result <> 0 then
|
|
Source.Next4; // ignore deprecated Hash32 checksum (0 stored now)
|
|
end;
|
|
|
|
function _BL_DynArray(Data: PAnsiChar; var Source: TFastReader; Info: PRttiInfo): PtrInt;
|
|
var
|
|
n, itemsize: PtrInt;
|
|
iteminfo: PRttiInfo;
|
|
load: TRttiBinaryLoad;
|
|
label
|
|
raw;
|
|
begin
|
|
iteminfo := Info^.DynArrayItemType(itemsize); // nil for unmanaged items
|
|
n := DynArrayLoadHeader(Source, Info, iteminfo);
|
|
if PPointer(Data)^ <> nil then
|
|
FastDynArrayClear(pointer(Data), iteminfo);
|
|
if n > 0 then
|
|
begin
|
|
DynArrayNew(pointer(Data), n, itemsize); // allocate zeroed memory
|
|
Data := PPointer(Data)^; // point to first item
|
|
if iteminfo = nil then
|
|
raw: Source.Copy(Data, itemsize * n)
|
|
else
|
|
begin
|
|
load := RTTI_BINARYLOAD[iteminfo^.Kind];
|
|
if Assigned(load) then
|
|
repeat
|
|
inc(Data, load(Data, Source, iteminfo));
|
|
dec(n);
|
|
until n = 0
|
|
else
|
|
goto raw;
|
|
end;
|
|
end;
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function DynArrayCompare(A, B: PAnsiChar; ExternalCountA, ExternalCountB: PInteger;
|
|
Info: PRttiInfo; CaseInSensitive: boolean): integer;
|
|
var
|
|
n1, n2, n: PtrInt;
|
|
begin
|
|
A := PPointer(A)^;
|
|
B := PPointer(B)^;
|
|
if A = B then
|
|
begin
|
|
result := 0;
|
|
exit;
|
|
end
|
|
else if A = nil then
|
|
begin
|
|
result := -1;
|
|
exit;
|
|
end
|
|
else if B = nil then
|
|
begin
|
|
result := 1;
|
|
exit;
|
|
end;
|
|
if ExternalCountA <> nil then
|
|
n1 := ExternalCountA^ // e.g. from TDynArray with external count
|
|
else
|
|
n1 := PDALen(A - _DALEN)^ + _DAOFF;
|
|
if ExternalCountB <> nil then
|
|
n2 := ExternalCountB^
|
|
else
|
|
n2 := PDALen(B - _DALEN)^ + _DAOFF;
|
|
n := n1;
|
|
if n > n2 then
|
|
n := n2;
|
|
if Info = TypeInfo(TObjectDynArray) then
|
|
result := ObjectCompare(PObject(A), PObject(B), n, CaseInSensitive)
|
|
else
|
|
result := BinaryCompare(A, B, Info^.DynArrayItemType, n, CaseInSensitive);
|
|
if result = 0 then
|
|
result := n1 - n2;
|
|
end;
|
|
|
|
function DynArrayAdd(TypeInfo: PRttiInfo; var DynArray; const Item): integer;
|
|
var
|
|
da: TDynArray;
|
|
begin
|
|
da.Init(TypeInfo, DynArray);
|
|
result := da.Add(Item);
|
|
end;
|
|
|
|
function DynArrayDelete(TypeInfo: PRttiInfo; var DynArray; Index: PtrInt): boolean;
|
|
var
|
|
da: TDynArray;
|
|
begin
|
|
da.Init(TypeInfo, DynArray);
|
|
result := da.Delete(Index);
|
|
end;
|
|
|
|
function DynArrayEquals(TypeInfo: PRttiInfo; var Array1, Array2;
|
|
Array1Count, Array2Count: PInteger; CaseInsensitive: boolean): boolean;
|
|
begin
|
|
result := DynArrayCompare(@Array1, @Array2, Array1Count, Array2Count,
|
|
TypeInfo, CaseInsensitive) = 0;
|
|
end;
|
|
|
|
{$ifdef FPCGENERICS}
|
|
|
|
function DynArrayAdd<TArray>(var DynArray: TArray; const Item): integer;
|
|
begin
|
|
result := DynArrayAdd(TypeInfo(TArray), DynArray, Item);
|
|
end;
|
|
|
|
function DynArrayDelete<TArray>(var DynArray: TArray; Index: PtrInt): boolean;
|
|
begin
|
|
result := DynArrayDelete(TypeInfo(TArray), DynArray, Index);
|
|
end;
|
|
|
|
function DynArrayCompare<TArray>(var Array1, Array2: TArray;
|
|
CaseInSensitive: boolean): integer;
|
|
begin
|
|
result := DynArrayCompare(
|
|
@Array1, @Array2, nil, nil, TypeInfo(TArray), CaseInSensitive);
|
|
end;
|
|
|
|
{$endif FPCGENERICS}
|
|
|
|
function _BC_DynArray(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := DynArrayCompare(A, B, nil, nil, Info, {caseinsens=}false);
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function _BCI_DynArray(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := DynArrayCompare(A, B, nil, nil, Info, {caseinsens=}true);
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function _BC_ObjArray(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := DynArrayCompare(
|
|
A, B, nil, nil, TypeInfo(TObjectDynArray), {caseinsens=}false);
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function _BCI_ObjArray(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := DynArrayCompare(
|
|
A, B, nil, nil, TypeInfo(TObjectDynArray), {caseinsens=}true);
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function _BS_Record(Data: PAnsiChar; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
|
|
var
|
|
fields: TRttiRecordManagedFields; // Size/Count/Fields
|
|
offset: PtrUInt;
|
|
f: PRttiRecordField;
|
|
begin
|
|
Info^.RecordManagedFields(fields);
|
|
f := fields.Fields;
|
|
fields.Fields := @RTTI_BINARYSAVE; // reuse pointer slot on stack
|
|
offset := 0;
|
|
while fields.Count <> 0 do
|
|
begin
|
|
dec(fields.Count);
|
|
Info := f^.{$ifdef HASDIRECTTYPEINFO}TypeInfo{$else}TypeInfoRef^{$endif};
|
|
{$ifdef FPC_OLDRTTI}
|
|
if Info^.Kind in rkManagedTypes then
|
|
{$endif FPC_OLDRTTI}
|
|
begin
|
|
offset := f^.Offset - offset;
|
|
if offset <> 0 then
|
|
begin
|
|
Dest.Write(Data, offset);
|
|
inc(Data, offset);
|
|
end;
|
|
offset := PRttiBinarySaves(fields.Fields)[Info^.Kind](Data, Dest, Info);
|
|
inc(Data, offset);
|
|
inc(offset, f^.Offset);
|
|
end;
|
|
inc(f);
|
|
end;
|
|
offset := PtrUInt(fields.Size) - offset;
|
|
if offset <> 0 then
|
|
Dest.Write(Data, offset);
|
|
result := fields.Size;
|
|
end;
|
|
|
|
function _BL_Record(Data: PAnsiChar; var Source: TFastReader; Info: PRttiInfo): PtrInt;
|
|
var
|
|
fields: TRttiRecordManagedFields; // Size/Count/Fields
|
|
offset: PtrUInt;
|
|
f: PRttiRecordField;
|
|
begin
|
|
Info^.RecordManagedFields(fields);
|
|
f := fields.Fields;
|
|
fields.Fields := @RTTI_BINARYLOAD; // reuse pointer slot on stack
|
|
offset := 0;
|
|
while fields.Count <> 0 do
|
|
begin
|
|
dec(fields.Count);
|
|
Info := f^.{$ifdef HASDIRECTTYPEINFO}TypeInfo{$else}TypeInfoRef^{$endif};
|
|
{$ifdef FPC_OLDRTTI}
|
|
if Info^.Kind in rkManagedTypes then
|
|
{$endif FPC_OLDRTTI}
|
|
begin
|
|
offset := f^.Offset - offset;
|
|
if offset <> 0 then
|
|
begin
|
|
Source.Copy(Data, offset);
|
|
inc(Data, offset);
|
|
end;
|
|
offset := PRttiBinaryLoads(fields.Fields)[Info^.Kind](Data, Source, Info);
|
|
inc(Data, offset);
|
|
inc(offset, f^.Offset);
|
|
end;
|
|
inc(f);
|
|
end;
|
|
offset := PtrUInt(fields.Size) - offset;
|
|
if offset <> 0 then
|
|
Source.Copy(Data, offset);
|
|
result := fields.Size;
|
|
end;
|
|
|
|
function _RecordCompare(A, B: PUtf8Char; Info: PRttiInfo;
|
|
CaseInSensitive: boolean): integer;
|
|
var
|
|
fields: TRttiRecordManagedFields; // Size/Count/Fields
|
|
offset: PtrUInt;
|
|
f: PRttiRecordField;
|
|
begin
|
|
Info^.RecordManagedFields(fields);
|
|
f := fields.Fields;
|
|
fields.Fields := @RTTI_COMPARE[CaseInSensitive]; // reuse pointer slot on stack
|
|
offset := 0;
|
|
if fields.Count <> 0 then
|
|
repeat
|
|
dec(fields.Count);
|
|
Info := f^.{$ifdef HASDIRECTTYPEINFO}TypeInfo{$else}TypeInfoRef^{$endif};
|
|
{$ifdef FPC_OLDRTTI}
|
|
if Info^.Kind in rkManagedTypes then
|
|
{$endif FPC_OLDRTTI}
|
|
begin
|
|
offset := f^.Offset - offset;
|
|
if offset <> 0 then
|
|
begin
|
|
result := MemCmp(pointer(A), pointer(B), offset); // binary comparison
|
|
if result <> 0 then
|
|
exit;
|
|
inc(A, offset);
|
|
inc(B, offset);
|
|
end;
|
|
offset := PRttiCompares(fields.Fields)[Info^.Kind](A, B, Info, result);
|
|
inc(A, offset);
|
|
inc(B, offset);
|
|
if result <> 0 then
|
|
exit;
|
|
inc(offset, f^.Offset);
|
|
end;
|
|
inc(f);
|
|
until fields.Count = 0
|
|
else
|
|
result := 0;
|
|
offset := PtrUInt(fields.Size) - offset;
|
|
if offset <> 0 then
|
|
result := MemCmp(pointer(A), pointer(B), offset); // trailing binary
|
|
end;
|
|
|
|
function _BC_Record(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
if A = B then
|
|
Compared := 0
|
|
else
|
|
Compared := _RecordCompare(A, B, Info, {caseinsens=}false);
|
|
result := Info^.RecordSize;
|
|
end;
|
|
|
|
function _BCI_Record(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
if A = B then
|
|
Compared := 0
|
|
else
|
|
Compared := _RecordCompare(A, B, Info, {caseinsens=}true);
|
|
result := Info^.RecordSize;
|
|
end;
|
|
|
|
function _BS_Array(Data: PAnsiChar; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
|
|
var
|
|
n: PtrInt;
|
|
sav: TRttiBinarySave;
|
|
label
|
|
raw;
|
|
begin
|
|
Info := Info^.ArrayItemType(n, result);
|
|
if Info = nil then
|
|
raw:Dest.Write(Data, result)
|
|
else
|
|
begin
|
|
sav := RTTI_BINARYSAVE[Info^.Kind];
|
|
if Assigned(sav) then // paranoid check
|
|
repeat
|
|
inc(Data, sav(Data, Dest, Info));
|
|
dec(n);
|
|
until n = 0
|
|
else
|
|
goto raw;
|
|
end;
|
|
end;
|
|
|
|
function _BL_Array(Data: PAnsiChar; var Source: TFastReader; Info: PRttiInfo): PtrInt;
|
|
var
|
|
n: PtrInt;
|
|
load: TRttiBinaryLoad;
|
|
label
|
|
raw;
|
|
begin
|
|
Info := Info^.ArrayItemType(n, result);
|
|
if Info = nil then
|
|
raw:Source.Copy(Data, result)
|
|
else
|
|
begin
|
|
load := RTTI_BINARYLOAD[Info^.Kind];
|
|
if Assigned(load) then // paranoid check
|
|
repeat
|
|
inc(Data, load(Data, Source, Info));
|
|
dec(n);
|
|
until n = 0
|
|
else
|
|
goto raw;
|
|
end;
|
|
end;
|
|
|
|
function _BC_Array(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
var
|
|
n: PtrInt;
|
|
begin
|
|
Info := Info^.ArrayItemType(n, result);
|
|
Compared := BinaryCompare(A, B, Info, n, {CaseInSensitive=}false);
|
|
end;
|
|
|
|
function _BCI_Array(A, B: pointer; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
var
|
|
n: PtrInt;
|
|
begin
|
|
Info := Info^.ArrayItemType(n, result);
|
|
Compared := BinaryCompare(A, B, Info, n, {CaseInSensitive=}true);
|
|
end;
|
|
|
|
procedure _BS_VariantComplex(Data: PVariant; Dest: TBufferWriter);
|
|
var
|
|
temp: RawUtf8;
|
|
begin
|
|
// not very fast, but creates valid JSON
|
|
_VariantSaveJson(Data^, twJsonEscape, temp);
|
|
Dest.Write(temp);
|
|
end;
|
|
|
|
procedure _BL_VariantComplex(Data: PVariant; var Source: TFastReader);
|
|
var
|
|
temp: TSynTempBuffer;
|
|
begin
|
|
Source.VarBlob(temp); // load into a private copy for in-place JSON parsing
|
|
try
|
|
BinaryVariantLoadAsJson(Data^, temp.buf, Source.CustomVariants);
|
|
finally
|
|
temp.Done;
|
|
end;
|
|
end;
|
|
|
|
const
|
|
// 0 for unserialized VType, 255 for valOleStr
|
|
VARIANT_SIZE: array[varEmpty .. varWord64] of byte = (
|
|
0, 0, 2, 4, 4, 8, 8, 8, 255, 0, 0, 2, 0, 0, 0, 0, 1, 1, 2, 4, 8, 8);
|
|
|
|
function _BS_Variant(Data: PVarData; Dest: TBufferWriter; Info: PRttiInfo): PtrInt;
|
|
var
|
|
vt: cardinal;
|
|
begin
|
|
Data := VarDataFromVariant(PVariant(Data)^); // handle varByRef
|
|
vt := Data^.VType;
|
|
Dest.Write2(vt);
|
|
if vt <= high(VARIANT_SIZE) then
|
|
begin
|
|
vt := VARIANT_SIZE[vt];
|
|
if vt <> 0 then
|
|
if vt = 255 then // valOleStr
|
|
Dest.WriteVar(Data^.vAny, length(WideString(Data^.vAny)) * 2)
|
|
else
|
|
Dest.Write(@Data^.VInt64, vt); // simple types are stored as binary
|
|
end
|
|
else if (vt = varString) and // expect only RawUtf8
|
|
(Data^.vAny <> nil) then
|
|
Dest.WriteVar(Data^.vAny, PStrLen(PAnsiChar(Data^.VAny) - _STRLEN)^)
|
|
{$ifdef HASVARUSTRING}
|
|
else if vt = varUString then
|
|
Dest.WriteVar(Data^.vAny, length(UnicodeString(Data^.vAny)) * 2)
|
|
{$endif HASVARUSTRING}
|
|
else
|
|
_BS_VariantComplex(pointer(Data), Dest);
|
|
result := SizeOf(Data^);
|
|
end;
|
|
|
|
function _BL_Variant(Data: PVarData; var Source: TFastReader; Info: PRttiInfo): PtrInt;
|
|
var
|
|
vt: cardinal;
|
|
begin
|
|
VarClear(PVariant(Data)^);
|
|
Source.Copy(@Data^.VType, 2);
|
|
Data^.VAny := nil; // to avoid GPF below
|
|
vt := Data^.VType;
|
|
if vt <= high(VARIANT_SIZE) then
|
|
begin
|
|
vt := VARIANT_SIZE[vt];
|
|
if vt <> 0 then
|
|
if vt = 255 then
|
|
with Source.VarBlob do // valOleStr
|
|
SetString(WideString(Data^.vAny), PWideChar(Ptr), Len shr 1)
|
|
else
|
|
Source.Copy(@Data^.VInt64, vt); // simple types
|
|
end
|
|
else if vt = varString then
|
|
with Source.VarBlob do
|
|
FastSetString(RawUtf8(Data^.vAny), Ptr, Len) // expect only RawUtf8
|
|
{$ifdef HASVARUSTRING}
|
|
else if vt = varUString then
|
|
with Source.VarBlob do
|
|
SetString(UnicodeString(Data^.vAny), PWideChar(Ptr), Len shr 1)
|
|
{$endif HASVARUSTRING}
|
|
else if Assigned(BinaryVariantLoadAsJson) then
|
|
_BL_VariantComplex(pointer(Data), Source)
|
|
else
|
|
Source.ErrorData('RTTI_BINARYLOAD[tkVariant] missing mormot.core.json.pas', []);
|
|
result := SizeOf(Data^);
|
|
end;
|
|
|
|
function _BC_Variant(A, B: PVarData; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
if A = B then
|
|
Compared := 0
|
|
else
|
|
Compared := SortDynArrayVariantComp(A^, B^, {caseinsens=}false);
|
|
result := SizeOf(variant);
|
|
end;
|
|
|
|
function _BCI_Variant(A, B: PVarData; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
if A = B then
|
|
Compared := 0
|
|
else
|
|
Compared := SortDynArrayVariantComp(A^, B^, {caseinsens=}true);
|
|
result := SizeOf(variant);
|
|
end;
|
|
|
|
function ObjectCompare(A, B: TObject; CaseInSensitive: boolean): integer;
|
|
var
|
|
rA, rB: TRttiCustom;
|
|
pA, pB: PRttiCustomProp;
|
|
i: integer;
|
|
begin
|
|
if (A = nil) or
|
|
(B = nil) or
|
|
(A = B) then
|
|
begin
|
|
result := ComparePointer(A, B);
|
|
exit;
|
|
end;
|
|
result := 0;
|
|
rA := Rtti.RegisterClass(A); // faster than RegisterType(Info)
|
|
pA := pointer(rA.Props.List);
|
|
if PClass(B)^.InheritsFrom(PClass(A)^) then
|
|
// same (or similar/inherited) class -> compare per exact properties
|
|
for i := 1 to rA.Props.Count do
|
|
begin
|
|
result := pA^.CompareValue(A, B, pA^, CaseInSensitive);
|
|
if result <> 0 then
|
|
exit;
|
|
inc(pA);
|
|
end
|
|
else
|
|
begin
|
|
// compare properties by name
|
|
rB := Rtti.RegisterClass(B);
|
|
for i := 1 to rA.Props.Count do
|
|
begin
|
|
if pA^.Name <> '' then
|
|
begin
|
|
pB := rB.Props.Find(pA^.Name);
|
|
if pB <> nil then // just ignore missing properties
|
|
begin
|
|
result := pA^.CompareValue(A, B, pB^, CaseInSensitive);
|
|
if result <> 0 then
|
|
exit;
|
|
end;
|
|
end;
|
|
inc(pA);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function _BC_Object(A, B: PObject; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := ObjectCompare(A^, B^, {caseinsens=}false);
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function _BCI_Object(A, B: PObject; Info: PRttiInfo; out Compared: integer): PtrInt;
|
|
begin
|
|
Compared := ObjectCompare(A^, B^, {caseinsens=}true);
|
|
result := SizeOf(pointer);
|
|
end;
|
|
|
|
function ObjectEquals(A, B: TObject): boolean;
|
|
begin
|
|
result := ObjectCompare(A, B, {caseinsensitive=}false) = 0;
|
|
end;
|
|
|
|
function ObjectEqualsI(A, B: TObject): boolean;
|
|
begin
|
|
result := ObjectCompare(A, B, {caseinsensitive=}true) = 0;
|
|
end;
|
|
|
|
function ObjectCompare(A, B: PObject; Count: PtrInt;
|
|
CaseInsensitive: boolean): integer;
|
|
begin
|
|
if Count > 0 then
|
|
repeat
|
|
result := ObjectCompare(A^, B^, CaseInsensitive);
|
|
if result <> 0 then
|
|
exit;
|
|
inc(A);
|
|
inc(B);
|
|
dec(Count);
|
|
until Count = 0;
|
|
result := 0;
|
|
end;
|
|
|
|
function BinaryEquals(A, B: pointer; Info: PRttiInfo; PSize: PInteger;
|
|
Kinds: TRttiKinds; CaseInSensitive: boolean): boolean;
|
|
var
|
|
size, comp: integer;
|
|
cmp: TRttiCompare;
|
|
begin
|
|
cmp := RTTI_COMPARE[CaseInSensitive, Info^.Kind];
|
|
if Assigned(cmp) and
|
|
(Info^.Kind in Kinds) then
|
|
begin
|
|
size := cmp(A, B, Info, comp);
|
|
if PSize <> nil then
|
|
PSize^ := size;
|
|
result := comp = 0;
|
|
end
|
|
else
|
|
result := false; // no fair comparison possible
|
|
end;
|
|
|
|
function BinaryCompare(A, B: pointer; Info: PRttiInfo;
|
|
CaseInSensitive: boolean): integer;
|
|
var
|
|
cmp: TRttiCompare;
|
|
begin
|
|
if A <> B then
|
|
if Info <> nil then
|
|
begin
|
|
cmp := RTTI_COMPARE[CaseInSensitive, Info^.Kind];
|
|
if Assigned(cmp) then
|
|
cmp(A, B, Info, result)
|
|
else
|
|
result := MemCmp(A, B, Info^.RttiSize);
|
|
end
|
|
else
|
|
result := ComparePointer(A, B)
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function BinaryCompare(A, B: pointer; Info: PRttiInfo; Count: PtrInt;
|
|
CaseInSensitive: boolean): integer;
|
|
var
|
|
cmp: TRttiCompare;
|
|
siz: PtrInt;
|
|
begin
|
|
if (A <> B) and
|
|
(Count > 0) then
|
|
if Info <> nil then
|
|
begin
|
|
cmp := RTTI_COMPARE[CaseInSensitive, Info^.Kind];
|
|
if Assigned(cmp) then
|
|
repeat
|
|
siz := cmp(A, B, Info, result);
|
|
inc(PAnsiChar(A), siz);
|
|
inc(PAnsiChar(B), siz);
|
|
if result <> 0 then
|
|
exit;
|
|
dec(Count);
|
|
until Count = 0
|
|
else
|
|
result := MemCmp(A, B, Count * Info^.RttiSize);
|
|
end
|
|
else
|
|
result := ComparePointer(A, B)
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
|
|
function BinarySaveLength(Data: pointer; Info: PRttiInfo; Len: PInteger;
|
|
Kinds: TRttiKinds): integer;
|
|
var
|
|
size: integer;
|
|
W: TBufferWriter; // not very fast, but good enough (RecordSave don't use it)
|
|
temp: array[byte] of byte; // will use mostly TFakeWriterStream.Write()
|
|
save: TRttiBinarySave;
|
|
begin
|
|
save := RTTI_BINARYSAVE[Info^.Kind];
|
|
if Assigned(save) and
|
|
(Info^.Kind in Kinds) then
|
|
begin
|
|
W := TBufferWriter.Create(TFakeWriterStream, @temp, SizeOf(temp));
|
|
try
|
|
size := save(Data, W, Info);
|
|
if Len <> nil then
|
|
Len^ := size;
|
|
result := W.TotalWritten;
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function BinarySave(Data: pointer; Dest: PAnsiChar; Info: PRttiInfo;
|
|
out Len: integer; Kinds: TRttiKinds): PAnsiChar;
|
|
var
|
|
W: TBufferWriter;
|
|
save: TRttiBinarySave;
|
|
begin
|
|
save := RTTI_BINARYSAVE[Info^.Kind];
|
|
if Assigned(save) and
|
|
(Info^.Kind in Kinds) then
|
|
begin
|
|
W := TBufferWriter.Create(TFakeWriterStream, Dest, 1 shl 30);
|
|
try
|
|
Len := save(Data, W, Info);
|
|
result := Dest + W.BufferPosition; // Dest as a 1GB temporary buffer :)
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
{$endif PUREMORMOT2}
|
|
|
|
procedure BinarySave(Data: pointer; Info: PRttiInfo; Dest: TBufferWriter);
|
|
var
|
|
save: TRttiBinarySave;
|
|
begin
|
|
save := RTTI_BINARYSAVE[Info^.Kind];
|
|
if Assigned(save) then
|
|
save(Data, Dest, Info);
|
|
end;
|
|
|
|
function BinarySave(Data: pointer; Info: PRttiInfo;
|
|
Kinds: TRttiKinds; WithCrc: boolean): RawByteString;
|
|
var
|
|
W: TBufferWriter;
|
|
temp: TTextWriterStackBuffer; // 8KB
|
|
save: TRttiBinarySave;
|
|
begin
|
|
save := RTTI_BINARYSAVE[Info^.Kind];
|
|
if Assigned(save) and
|
|
(Info^.Kind in Kinds) then
|
|
begin
|
|
W := TBufferWriter.Create(temp{%H-});
|
|
try
|
|
if WithCrc then
|
|
W.Write4(0);
|
|
save(Data, W, Info);
|
|
result := W.FlushTo;
|
|
if WithCrc then
|
|
PCardinal(result)^ :=
|
|
crc32c(0, @PCardinalArray(result)[1], length(result) - 4);
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end
|
|
else
|
|
result := '';
|
|
end;
|
|
|
|
function BinarySaveBytes(Data: pointer; Info: PRttiInfo;
|
|
Kinds: TRttiKinds): TBytes;
|
|
var
|
|
W: TBufferWriter;
|
|
temp: TTextWriterStackBuffer; // 8KB
|
|
save: TRttiBinarySave;
|
|
begin
|
|
save := RTTI_BINARYSAVE[Info^.Kind];
|
|
if Assigned(save) and
|
|
(Info^.Kind in Kinds) then
|
|
begin
|
|
W := TBufferWriter.Create(temp{%H-});
|
|
try
|
|
save(Data, W, Info);
|
|
result := W.FlushToBytes;
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
procedure BinarySave(Data: pointer; var Dest: TSynTempBuffer; Info: PRttiInfo;
|
|
Kinds: TRttiKinds; WithCrc: boolean);
|
|
var
|
|
W: TBufferWriter;
|
|
save: TRttiBinarySave;
|
|
begin
|
|
save := RTTI_BINARYSAVE[Info^.Kind];
|
|
if Assigned(save) and
|
|
(Info^.Kind in Kinds) then
|
|
begin
|
|
W := TBufferWriter.Create(TRawByteStringStream, @Dest.tmp,
|
|
SizeOf(Dest.tmp) - 16); // Dest.Init() reserves 16 additional bytes
|
|
try
|
|
if WithCrc then
|
|
W.Write4(0);
|
|
save(Data, W, Info);
|
|
if W.Stream.Position = 0 then
|
|
// only Dest.tmp buffer was used -> just set the proper size
|
|
Dest.Init(W.TotalWritten)
|
|
else
|
|
// more than 4KB -> temporary allocation through the temp RawByteString
|
|
Dest.Init(W.FlushTo);
|
|
if WithCrc then
|
|
PCardinal(Dest.buf)^ :=
|
|
crc32c(0, @PCardinalArray(Dest.buf)[1], Dest.len - 4);
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end
|
|
else
|
|
Dest.Init(0);
|
|
end;
|
|
|
|
function BinarySaveBase64(Data: pointer; Info: PRttiInfo; UriCompatible: boolean;
|
|
Kinds: TRttiKinds; WithCrc: boolean): RawUtf8;
|
|
var
|
|
W: TBufferWriter;
|
|
temp: TTextWriterStackBuffer; // 8KB
|
|
tmp: RawByteString;
|
|
P: PAnsiChar;
|
|
len: integer;
|
|
save: TRttiBinarySave;
|
|
begin
|
|
save := RTTI_BINARYSAVE[Info^.Kind];
|
|
if Assigned(save) and
|
|
(Info^.Kind in Kinds) then
|
|
begin
|
|
W := TBufferWriter.Create(temp{%H-});
|
|
try
|
|
if WithCrc then
|
|
// placeholder for the trailing crc32c
|
|
W.Write4(0);
|
|
save(Data, W, Info);
|
|
len := W.TotalWritten;
|
|
if W.Stream.Position = 0 then
|
|
// only temp buffer was used
|
|
P := pointer(@temp)
|
|
else
|
|
begin
|
|
// more than 8KB -> temporary allocation
|
|
tmp := W.FlushTo;
|
|
P := pointer(tmp);
|
|
end;
|
|
if WithCrc then
|
|
// as mORMot 1.18 RecordSaveBase64()
|
|
PCardinal(P)^ := crc32c(0, P + 4, len - 4);
|
|
if UriCompatible then
|
|
result := BinToBase64uri(P, len)
|
|
else
|
|
result := BinToBase64(P, len);
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end
|
|
else
|
|
result := '';
|
|
end;
|
|
|
|
function BinaryLoad(Data: pointer; Source: PAnsiChar; Info: PRttiInfo;
|
|
Len: PInteger; SourceMax: PAnsiChar; Kinds: TRttiKinds;
|
|
TryCustomVariants: PDocVariantOptions): PAnsiChar;
|
|
var
|
|
size: integer;
|
|
read: TFastReader;
|
|
load: TRttiBinaryLoad;
|
|
begin
|
|
load := RTTI_BINARYLOAD[Info^.Kind];
|
|
if Assigned(load) and
|
|
(Info^.Kind in Kinds) and
|
|
(SourceMax <> nil) then
|
|
begin
|
|
{%H-}read.Init(Source, SourceMax - Source);
|
|
read.CustomVariants := TryCustomVariants;
|
|
size := load(Data, read, Info);
|
|
if Len <> nil then
|
|
Len^ := size;
|
|
result := read.P;
|
|
end
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
function BinaryLoad(Data: pointer; const Source: RawByteString; Info: PRttiInfo;
|
|
Kinds: TRttiKinds; TryCustomVariants: PDocVariantOptions): boolean;
|
|
var
|
|
P: PAnsiChar;
|
|
begin
|
|
if Info^.Kind in Kinds then
|
|
begin
|
|
P := pointer(Source);
|
|
P := BinaryLoad(Data, P, Info, nil, P + length(Source), Kinds, TryCustomVariants);
|
|
result := (P <> nil) and
|
|
(P - pointer(Source) = length(Source));
|
|
end
|
|
else
|
|
result := false;
|
|
end;
|
|
|
|
function BinaryLoadBase64(Source: PAnsiChar; Len: PtrInt; Data: pointer;
|
|
Info: PRttiInfo; UriCompatible: boolean; Kinds: TRttiKinds;
|
|
WithCrc: boolean; TryCustomVariants: PDocVariantOptions): boolean;
|
|
var
|
|
temp: TSynTempBuffer;
|
|
tempend: pointer;
|
|
begin
|
|
if (Len > 6) and
|
|
(Info^.Kind in Kinds) then
|
|
begin
|
|
if UriCompatible then
|
|
result := Base64uriToBin(Source, Len, temp)
|
|
else
|
|
result := Base64ToBin(Source, Len, temp);
|
|
tempend := PAnsiChar(temp.buf) + temp.len;
|
|
if result then
|
|
if WithCrc then
|
|
result := (temp.len >= 4) and
|
|
(crc32c(0, PAnsiChar(temp.buf) + 4, temp.len - 4) = PCardinal(temp.buf)^) and
|
|
(BinaryLoad(Data, PAnsiChar(temp.buf) + 4, Info, nil, tempend,
|
|
Kinds, TryCustomVariants) = tempend)
|
|
else
|
|
result := (BinaryLoad(Data, temp.buf, Info, nil, tempend,
|
|
Kinds, TryCustomVariants) = tempend);
|
|
temp.Done;
|
|
end
|
|
else
|
|
result := false;
|
|
end;
|
|
|
|
|
|
function RecordEquals(const RecA, RecB; TypeInfo: PRttiInfo; PRecSize: PInteger;
|
|
CaseInSensitive: boolean): boolean;
|
|
begin
|
|
result := BinaryEquals(@RecA, @RecB, TypeInfo, PRecSize,
|
|
rkRecordTypes, CaseInSensitive);
|
|
end;
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
|
|
function RecordSaveLength(const Rec; TypeInfo: PRttiInfo; Len: PInteger): integer;
|
|
begin
|
|
result := {%H-}BinarySaveLength(@Rec, TypeInfo, Len, rkRecordTypes);
|
|
end;
|
|
|
|
function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: PRttiInfo;
|
|
out Len: integer): PAnsiChar;
|
|
begin
|
|
result := {%H-}BinarySave(@Rec, Dest, TypeInfo, Len, rkRecordTypes);
|
|
end;
|
|
|
|
function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: PRttiInfo): PAnsiChar;
|
|
var
|
|
dummylen: integer;
|
|
begin
|
|
result := {%H-}BinarySave(@Rec, Dest, TypeInfo, dummylen, rkRecordTypes);
|
|
end;
|
|
|
|
{$endif PUREMORMOT2}
|
|
|
|
function RecordSave(const Rec; TypeInfo: PRttiInfo): RawByteString;
|
|
begin
|
|
result := BinarySave(@Rec, TypeInfo, rkRecordTypes);
|
|
end;
|
|
|
|
function RecordSaveBytes(const Rec; TypeInfo: PRttiInfo): TBytes;
|
|
begin
|
|
result := BinarySaveBytes(@Rec, TypeInfo, rkRecordTypes);
|
|
end;
|
|
|
|
procedure RecordSave(const Rec; var Dest: TSynTempBuffer; TypeInfo: PRttiInfo);
|
|
begin
|
|
BinarySave(@Rec, Dest, TypeInfo, rkRecordTypes);
|
|
end;
|
|
|
|
function RecordSaveBase64(const Rec; TypeInfo: PRttiInfo; UriCompatible: boolean): RawUtf8;
|
|
begin
|
|
result := BinarySaveBase64(@Rec, TypeInfo, UriCompatible, rkRecordTypes);
|
|
end;
|
|
|
|
function RecordLoad(var Rec; Source: PAnsiChar; TypeInfo: PRttiInfo;
|
|
Len: PInteger; SourceMax: PAnsiChar; TryCustomVariants: PDocVariantOptions): PAnsiChar;
|
|
begin
|
|
{$ifndef PUREMORMOT2}
|
|
if SourceMax = nil then
|
|
// mORMot 1 unsafe backward compatible: assume fake 100MB Source input
|
|
SourceMax := Source + 100 shl 20;
|
|
{$endif PUREMORMOT2}
|
|
result := BinaryLoad(@Rec, Source, TypeInfo, Len, SourceMax,
|
|
rkRecordTypes, TryCustomVariants);
|
|
end;
|
|
|
|
function RecordLoad(var Rec; const Source: RawByteString; TypeInfo: PRttiInfo;
|
|
TryCustomVariants: PDocVariantOptions): boolean;
|
|
begin
|
|
result := BinaryLoad(@Rec, Source, TypeInfo, rkRecordTypes, TryCustomVariants);
|
|
end;
|
|
|
|
function RecordLoadBase64(Source: PAnsiChar; Len: PtrInt; var Rec;
|
|
TypeInfo: PRttiInfo; UriCompatible: boolean; TryCustomVariants: PDocVariantOptions): boolean;
|
|
begin
|
|
result := BinaryLoadBase64(Source, Len, @Rec, TypeInfo, UriCompatible,
|
|
rkRecordTypes, {withcrc=}true, TryCustomVariants);
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
{ ************ TDynArray and TDynArrayHashed Wrappers }
|
|
|
|
const
|
|
// helper arrays to get the standard comparison/hash functions
|
|
PT_SORT: array[{caseins=}boolean, TRttiParserType] of TDynArraySortCompare = (
|
|
// case sensitive comparison/sort functions:
|
|
(nil, // ptNone
|
|
nil, // ptArray
|
|
SortDynArrayBoolean, // ptBoolean
|
|
SortDynArrayByte, // ptByte
|
|
SortDynArrayCardinal, // ptCardinal
|
|
SortDynArrayInt64, // ptCurrency
|
|
SortDynArrayDouble, // ptDouble
|
|
SortDynArrayExtended, // ptExtended
|
|
SortDynArrayInt64, // ptInt64
|
|
SortDynArrayInteger, // ptInteger
|
|
SortDynArrayQWord, // ptQWord
|
|
{$ifdef CPUINTEL}SortDynArrayAnsiString
|
|
{$else}SortDynArrayRawByteString{$endif}, // ptRawByteString
|
|
SortDynArrayAnsiString, // ptRawJson
|
|
SortDynArrayAnsiString, // ptRawUtf8
|
|
nil, // ptRecord
|
|
SortDynArraySingle, // ptSingle
|
|
{$ifdef UNICODE}SortDynArrayString
|
|
{$else}SortDynArrayAnsiString{$endif}, // ptString
|
|
SortDynArrayUnicodeString, // ptSynUnicode
|
|
SortDynArrayDouble, // ptDateTime
|
|
SortDynArrayDouble, // ptDateTimeMS
|
|
SortDynArray128, // ptGuid
|
|
SortDynArray128, // ptHash128
|
|
SortDynArray256, // ptHash256
|
|
SortDynArray512, // ptHash512
|
|
SortDynArrayInt64, // ptOrm
|
|
SortDynArrayInt64, // ptTimeLog
|
|
SortDynArrayUnicodeString, // ptUnicodeString
|
|
SortDynArrayInt64, // ptUnixTime
|
|
SortDynArrayInt64, // ptUnixMSTime
|
|
SortDynArrayVariant, // ptVariant
|
|
SortDynArrayUnicodeString, // ptWideString
|
|
SortDynArrayAnsiString, // ptWinAnsi
|
|
SortDynArrayWord, // ptWord
|
|
nil, // ptEnumeration
|
|
nil, // ptSet
|
|
SortDynArrayPointer, // ptClass
|
|
nil, // ptDynArray
|
|
SortDynArrayPointer, // ptInterface
|
|
SortDynArrayPUtf8Char, // ptPUtf8Char
|
|
nil), // ptCustom
|
|
// case insensitive comparison/sort functions:
|
|
(nil, // ptNone
|
|
nil, // ptArray
|
|
SortDynArrayBoolean, // ptBoolean
|
|
SortDynArrayByte, // ptByte
|
|
SortDynArrayCardinal, // ptCardinal
|
|
SortDynArrayInt64, // ptCurrency
|
|
SortDynArrayDouble, // ptDouble
|
|
SortDynArrayExtended, // ptExtended
|
|
SortDynArrayInt64, // ptInt64
|
|
SortDynArrayInteger, // ptInteger
|
|
SortDynArrayQWord, // ptQWord
|
|
{$ifdef CPUINTEL}SortDynArrayAnsiString
|
|
{$else}SortDynArrayRawByteString{$endif}, // ptRawByteString
|
|
SortDynArrayAnsiStringI, // ptRawJson
|
|
SortDynArrayAnsiStringI, // ptRawUtf8
|
|
nil, // ptRecord
|
|
SortDynArraySingle, // ptSingle
|
|
SortDynArrayStringI, // ptString
|
|
SortDynArrayUnicodeStringI, // ptSynUnicode
|
|
SortDynArrayDouble, // ptDateTime
|
|
SortDynArrayDouble, // ptDateTimeMS
|
|
SortDynArray128, // ptGuid
|
|
SortDynArray128, // ptHash128
|
|
SortDynArray256, // ptHash256
|
|
SortDynArray512, // ptHash512
|
|
SortDynArrayInt64, // ptOrm
|
|
SortDynArrayInt64, // ptTimeLog
|
|
SortDynArrayUnicodeStringI, // ptUnicodeString
|
|
SortDynArrayInt64, // ptUnixTime
|
|
SortDynArrayInt64, // ptUnixMSTime
|
|
SortDynArrayVariantI, // ptVariant
|
|
SortDynArrayUnicodeStringI, // ptWideString
|
|
SortDynArrayAnsiStringI, // ptWinAnsi
|
|
SortDynArrayWord, // ptWord
|
|
nil, // ptEnumeration
|
|
nil, // ptSet
|
|
SortDynArrayPointer, // ptClass
|
|
nil, // ptDynArray
|
|
SortDynArrayPointer, // ptInterface
|
|
SortDynArrayPUtf8CharI, // ptPUtf8Char
|
|
nil)); // ptCustom
|
|
|
|
function DynArraySortOne(Kind: TRttiParserType;
|
|
CaseInsensitive: boolean): TDynArraySortCompare;
|
|
begin
|
|
result := PT_SORT[CaseInsensitive, Kind];
|
|
end;
|
|
|
|
procedure ObjArraySort(var aValue; Compare: TDynArraySortCompare;
|
|
CountPointer: PInteger);
|
|
begin
|
|
DynArray(TypeInfo(TObjectDynArray), aValue, CountPointer).Sort(Compare);
|
|
end;
|
|
|
|
|
|
{ TDynArray }
|
|
|
|
procedure TDynArray.InitRtti(aInfo: TRttiCustom; var aValue;
|
|
aCountPointer: PInteger);
|
|
begin
|
|
fInfo := aInfo;
|
|
fValue := @aValue;
|
|
fCountP := aCountPointer;
|
|
if fCountP <> nil then
|
|
fCountP^ := 0;
|
|
fCompare := nil;
|
|
fSorted := false;
|
|
fNoFinalize := false;
|
|
end;
|
|
|
|
procedure TDynArray.InitRtti(aInfo: TRttiCustom; var aValue);
|
|
begin
|
|
fInfo := aInfo;
|
|
fValue := @aValue;
|
|
fCountP := nil;
|
|
fCompare := nil;
|
|
fSorted := false;
|
|
fNoFinalize := false;
|
|
end;
|
|
|
|
procedure TDynArray.Init(aTypeInfo: PRttiInfo; var aValue;
|
|
aCountPointer: PInteger);
|
|
begin
|
|
if aTypeInfo^.Kind <> rkDynArray then
|
|
raise EDynArray.CreateUtf8('TDynArray.Init: % is %, expected rkDynArray',
|
|
[aTypeInfo.RawName, ToText(aTypeInfo.Kind)^]);
|
|
InitRtti(Rtti.RegisterType(aTypeInfo), aValue, aCountPointer);
|
|
end;
|
|
|
|
function TDynArray.InitSpecific(aTypeInfo: PRttiInfo; var aValue;
|
|
aKind: TRttiParserType; aCountPointer: PInteger; aCaseInsensitive: boolean): TRttiParserType;
|
|
begin
|
|
if aTypeInfo^.Kind <> rkDynArray then
|
|
raise EDynArray.CreateUtf8('TDynArray.InitSpecific: % is %, expected rkDynArray',
|
|
[aTypeInfo.RawName, ToText(aTypeInfo.Kind)^]);
|
|
InitRtti(Rtti.RegisterType(aTypeInfo), aValue, aCountPointer);
|
|
result := SetParserType(aKind, aCaseInsensitive);
|
|
end;
|
|
|
|
function TDynArray.SetParserType(aKind: TRttiParserType;
|
|
aCaseInsensitive: boolean): TRttiParserType;
|
|
begin
|
|
case aKind of
|
|
ptNone:
|
|
if Assigned(fInfo.ArrayRtti) then
|
|
result := fInfo.ArrayRtti.Parser
|
|
else
|
|
result := fInfo.ArrayFirstField;
|
|
else
|
|
result := aKind;
|
|
end;
|
|
fCompare := PT_SORT[aCaseInsensitive, result];
|
|
if not Assigned(fCompare) then
|
|
if result = ptVariant then
|
|
raise EDynArray.CreateUtf8('TDynArray.SetParserType(%): missing mormot.core.json',
|
|
[Info.Name, ToText(result)^])
|
|
else if aKind <> ptNone then
|
|
raise EDynArray.CreateUtf8('TDynArray.SetParserType(%) unsupported %',
|
|
[Info.Name, ToText(result)^]);
|
|
end;
|
|
|
|
function TDynArray.ItemSize: PtrUInt;
|
|
begin
|
|
result := fInfo.Cache.ItemSize;
|
|
end;
|
|
|
|
function TDynArray.GetCount: PtrInt;
|
|
begin // use result as a single temporary variable for better FPC asm generation
|
|
result := PtrUInt(fCountP);
|
|
if result <> 0 then
|
|
result := PInteger(result)^ // count is external
|
|
else
|
|
begin
|
|
result := PtrUInt(fValue);
|
|
if result <> 0 then
|
|
begin
|
|
result := PPtrInt(result)^;
|
|
if result <> 0 then
|
|
begin
|
|
result := PDALen(result - _DALEN)^; // count = length()
|
|
{$ifdef FPC} inc(result, _DAOFF); {$endif}
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TDynArray.GetCapacity: PtrInt;
|
|
begin
|
|
result := PtrInt(fValue);
|
|
if result <> 0 then
|
|
begin
|
|
result := PPtrInt(result)^;
|
|
if result <> 0 then
|
|
begin
|
|
result := PDALen(result - _DALEN)^; // capacity = length()
|
|
{$ifdef FPC} inc(result, _DAOFF); {$endif}
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TDynArray.ItemCopy(Source, Dest: pointer);
|
|
var
|
|
nfo: TRttiCustom;
|
|
begin
|
|
nfo := fInfo.ArrayRtti;
|
|
if (nfo <> nil) and // inlined nfo.ValueCopy() to avoid MoveFast() twice
|
|
Assigned(nfo.Copy) then
|
|
nfo.Copy(Dest, Source, nfo.Info) // also for T*ObjArray
|
|
else
|
|
MoveFast(Source^, Dest^, fInfo.Cache.ItemSize);
|
|
end;
|
|
|
|
procedure TDynArray.ItemClear(Item: pointer);
|
|
begin
|
|
if Item = nil then
|
|
exit;
|
|
if (fInfo.ArrayRtti <> nil) and
|
|
not fNoFinalize then
|
|
fInfo.ArrayRtti.ValueFinalize(Item); // also for T*ObjArray
|
|
FillCharFast(Item^, fInfo.Cache.ItemSize, 0); // always
|
|
end;
|
|
|
|
procedure TDynArray.ItemRandom(Item: pointer);
|
|
begin
|
|
if Item <> nil then
|
|
if fInfo.ArrayRtti <> nil then
|
|
fInfo.ArrayRtti.ValueRandom(Item)
|
|
else
|
|
SharedRandom.Fill(Item, fInfo.Cache.ItemSize);
|
|
end;
|
|
|
|
function TDynArray.ItemEquals(A, B: pointer; CaseInSensitive: boolean): boolean;
|
|
begin
|
|
result := ItemCompare(A, B, CaseInSensitive) = 0;
|
|
end;
|
|
|
|
function TDynArray.ItemCompare(A, B: pointer; CaseInSensitive: boolean): integer;
|
|
var
|
|
comp: TRttiCompare;
|
|
rtti: PRttiInfo;
|
|
label
|
|
bin;
|
|
begin
|
|
if Assigned(fCompare) then
|
|
result := fCompare(A^, B^)
|
|
else if not(rcfArrayItemManaged in fInfo.Flags) then
|
|
bin: // fast binary comparison with length
|
|
result := MemCmp(A, B, fInfo.Cache.ItemSize)
|
|
else
|
|
begin
|
|
rtti := fInfo.Cache.ItemInfo; // <> nil for managed items
|
|
comp := RTTI_COMPARE[CaseInsensitive, rtti.Kind];
|
|
if Assigned(comp) then
|
|
comp(A, B, rtti, result)
|
|
else
|
|
goto bin;
|
|
end;
|
|
end;
|
|
|
|
function TDynArray.Add(const Item): PtrInt;
|
|
begin
|
|
result := GetCount;
|
|
if fValue = nil then
|
|
exit; // avoid GPF if void
|
|
SetCount(result + 1);
|
|
ItemCopy(@Item, PAnsiChar(fValue^) + result * fInfo.Cache.ItemSize);
|
|
end;
|
|
|
|
function TDynArray.New: PtrInt;
|
|
begin
|
|
result := GetCount;
|
|
SetCount(result + 1);
|
|
end;
|
|
|
|
function TDynArray.NewPtr: pointer;
|
|
var
|
|
index: PtrInt;
|
|
begin
|
|
index := GetCount; // in two explicit steps to ensure no problem at inlining
|
|
SetCount(index + 1);
|
|
result := PAnsiChar(fValue^) + index * fInfo.Cache.ItemSize;
|
|
end;
|
|
|
|
function TDynArray.Peek(var Dest): boolean;
|
|
var
|
|
index: PtrInt;
|
|
begin
|
|
index := GetCount - 1;
|
|
result := index >= 0;
|
|
if result then
|
|
ItemCopy(PAnsiChar(fValue^) + index * fInfo.Cache.ItemSize, @Dest);
|
|
end;
|
|
|
|
function TDynArray.Pop(var Dest): boolean;
|
|
var
|
|
index: PtrInt;
|
|
begin
|
|
index := GetCount - 1;
|
|
result := index >= 0;
|
|
if result then
|
|
begin
|
|
ItemMoveTo(index, @Dest);
|
|
SetCount(index);
|
|
end;
|
|
end;
|
|
|
|
function TDynArray.PeekHead(var Dest): boolean;
|
|
begin
|
|
result := GetCount <> 0;
|
|
if result then
|
|
ItemCopy(fValue^, @Dest);
|
|
end;
|
|
|
|
function TDynArray.PopHead(var Dest): boolean;
|
|
begin
|
|
result := GetCount <> 0;
|
|
if result then
|
|
begin
|
|
ItemMoveTo(0, @Dest);
|
|
Delete(0);
|
|
end;
|
|
end;
|
|
|
|
procedure TDynArray.Insert(Index: PtrInt; const Item);
|
|
var
|
|
n: PtrInt;
|
|
s: PtrUInt;
|
|
P: PAnsiChar;
|
|
begin
|
|
if fValue = nil then
|
|
exit; // avoid GPF if void
|
|
n := GetCount;
|
|
SetCount(n + 1);
|
|
s := fInfo.Cache.ItemSize;
|
|
if PtrUInt(Index) < PtrUInt(n) then
|
|
begin
|
|
// reserve space for the new item
|
|
P := PAnsiChar(fValue^) + PtrUInt(Index) * s;
|
|
MoveFast(P[0], P[s], PtrUInt(n - Index) * s);
|
|
if rcfArrayItemManaged in fInfo.Flags then // avoid GPF in ItemCopy() below
|
|
FillCharFast(P^, s, 0);
|
|
end
|
|
else
|
|
// Index>=Count -> add at the end
|
|
P := PAnsiChar(fValue^) + PtrUInt(n) * s;
|
|
ItemCopy(@Item, P);
|
|
end;
|
|
|
|
procedure TDynArray.Clear;
|
|
begin
|
|
SetCount(0);
|
|
end;
|
|
|
|
function TDynArray.ClearSafe: boolean;
|
|
begin
|
|
try
|
|
SetCount(0);
|
|
result := true;
|
|
except // weak code, but may be a good idea in a destructor
|
|
result := false;
|
|
end;
|
|
end;
|
|
|
|
function TDynArray.Delete(aIndex: PtrInt): boolean;
|
|
var
|
|
n: PtrInt;
|
|
s, len: PtrUInt;
|
|
P: PAnsiChar;
|
|
wassorted: boolean;
|
|
begin
|
|
result := false;
|
|
if fValue = nil then
|
|
exit; // avoid GPF if void
|
|
n := GetCount;
|
|
if PtrUInt(aIndex) >= PtrUInt(n) then
|
|
exit; // out of range
|
|
if PDACnt(PAnsiChar(fValue^) - _DACNT)^ > 1 then
|
|
InternalSetLength(n, n); // unique
|
|
dec(n);
|
|
s := fInfo.Cache.ItemSize;
|
|
P := PAnsiChar(fValue^) + PtrUInt(aIndex) * s;
|
|
if (fInfo.ArrayRtti <> nil) and
|
|
not fNoFinalize then
|
|
fInfo.ArrayRtti.ValueFinalize(P); // also for T*ObjArray
|
|
len := n - aIndex;
|
|
if len <> 0 then
|
|
begin
|
|
len := len * s;
|
|
MoveFast(P[s], P[0], len);
|
|
inc(P, len);
|
|
end;
|
|
FillCharFast(P^, s, 0);
|
|
wassorted := fSorted;
|
|
SetCount(n); // won't reallocate
|
|
fSorted := wassorted; // deletion won't change the order
|
|
result := true;
|
|
end;
|
|
|
|
{$ifdef FPC} // very efficient inlined code on FPC
|
|
function TDynArray.ItemPtr(index: PtrInt): pointer;
|
|
label
|
|
ok, ko; // labels make the code shorter and more efficient
|
|
var
|
|
c: PtrUInt;
|
|
begin
|
|
result := pointer(fValue);
|
|
if result = nil then
|
|
exit;
|
|
result := PPointer(result)^;
|
|
if result = nil then
|
|
exit;
|
|
c := PtrUInt(fCountP);
|
|
if c <> 0 then
|
|
begin
|
|
if PtrUInt(index) < PCardinal(c)^ then
|
|
ok: inc(PByte(result), index * fInfo.Cache.ItemSize) // branchless ext count
|
|
else
|
|
goto ko;
|
|
end
|
|
else // FPC stores high() in TDALen=PtrInt
|
|
if PtrUInt(index) <= PPtrUInt(PAnsiChar(result) - _DALEN)^ then
|
|
goto ok
|
|
else
|
|
ko: result := nil;
|
|
end;
|
|
{$else} // latest Delphi compilers have troubles with inlining + labels
|
|
function TDynArray.ItemPtr(index: PtrInt): pointer;
|
|
var
|
|
c: PtrUInt;
|
|
begin
|
|
result := pointer(fValue);
|
|
if result = nil then
|
|
exit;
|
|
result := PPointer(result)^;
|
|
if result = nil then
|
|
exit;
|
|
c := PtrUInt(fCountP);
|
|
if c <> 0 then
|
|
if PtrUInt(index) < PCardinal(c)^ then
|
|
inc(PByte(result), index * fInfo.Cache.ItemSize) // branchless ext count
|
|
else
|
|
result := nil
|
|
else // Delphi stores length() in TDALen=NativeInt
|
|
if PtrUInt(index) < PPtrUInt(PtrUInt(result) - _DALEN)^ then
|
|
inc(PByte(result), index * fInfo.Cache.ItemSize)
|
|
else
|
|
result := nil;
|
|
end;
|
|
{$endif FPC}
|
|
|
|
function TDynArray.ItemCopyAt(index: PtrInt; Dest: pointer): boolean;
|
|
var
|
|
p: pointer;
|
|
begin
|
|
p := ItemPtr(index);
|
|
if p <> nil then
|
|
begin
|
|
ItemCopy(p, Dest);
|
|
result := true;
|
|
end
|
|
else
|
|
result := false;
|
|
end;
|
|
|
|
function TDynArray.ItemMoveTo(index: PtrInt; Dest: pointer): boolean;
|
|
var
|
|
p: pointer;
|
|
begin
|
|
p := ItemPtr(index);
|
|
if (p = nil) or
|
|
(Dest = nil) then
|
|
begin
|
|
result := false;
|
|
exit;
|
|
end;
|
|
if (fInfo.ArrayRtti <> nil) and
|
|
not fNoFinalize then
|
|
fInfo.ArrayRtti.ValueFinalize(Dest); // also handle T*ObjArray
|
|
MoveFast(p^, Dest^, fInfo.Cache.ItemSize);
|
|
FillCharFast(p^, fInfo.Cache.ItemSize, 0);
|
|
result := true;
|
|
end;
|
|
|
|
procedure TDynArray.ItemCopyFrom(Source: pointer; index: PtrInt;
|
|
ClearBeforeCopy: boolean);
|
|
var
|
|
p: pointer;
|
|
begin
|
|
p := ItemPtr(index);
|
|
if p <> nil then
|
|
begin
|
|
if ClearBeforeCopy then // safer if Source is a copy of p^
|
|
ItemClear(p);
|
|
ItemCopy(Source, p);
|
|
end;
|
|
end;
|
|
|
|
{$ifdef CPU64}
|
|
procedure Exchg16(P1, P2: PPtrIntArray); inline;
|
|
var
|
|
c: PtrInt;
|
|
begin
|
|
c := P1[0];
|
|
P1[0] := P2[0];
|
|
P2[0] := c;
|
|
c := P1[1];
|
|
P1[1] := P2[1];
|
|
P2[1] := c;
|
|
end;
|
|
{$endif CPU64}
|
|
|
|
procedure TDynArray.Reverse;
|
|
var
|
|
n, siz: PtrInt;
|
|
P1, P2: PAnsiChar;
|
|
c: AnsiChar;
|
|
i32: integer;
|
|
i64: Int64;
|
|
begin
|
|
n := GetCount - 1;
|
|
if n > 0 then
|
|
begin
|
|
siz := fInfo.Cache.ItemSize;
|
|
P1 := fValue^;
|
|
case siz of
|
|
1:
|
|
begin
|
|
// optimized version for TByteDynArray and such
|
|
P2 := P1 + n;
|
|
while P1 < P2 do
|
|
begin
|
|
c := P1^;
|
|
P1^ := P2^;
|
|
P2^ := c;
|
|
inc(P1);
|
|
dec(P2);
|
|
end;
|
|
end;
|
|
4:
|
|
begin
|
|
// optimized version for TIntegerDynArray and such
|
|
P2 := P1 + n * SizeOf(integer);
|
|
while P1 < P2 do
|
|
begin
|
|
i32 := PInteger(P1)^;
|
|
PInteger(P1)^ := PInteger(P2)^;
|
|
PInteger(P2)^ := i32;
|
|
inc(P1, 4);
|
|
dec(P2, 4);
|
|
end;
|
|
end;
|
|
8:
|
|
begin
|
|
// optimized version for TInt64DynArray + TDoubleDynArray and such
|
|
P2 := P1 + n * SizeOf(Int64);
|
|
while P1 < P2 do
|
|
begin
|
|
i64 := PInt64(P1)^;
|
|
PInt64(P1)^ := PInt64(P2)^;
|
|
PInt64(P2)^ := i64;
|
|
inc(P1, 8);
|
|
dec(P2, 8);
|
|
end;
|
|
end;
|
|
16:
|
|
begin
|
|
// optimized version for 32-bit TVariantDynArray and such
|
|
P2 := P1 + n * 16;
|
|
while P1 < P2 do
|
|
begin
|
|
{$ifdef CPU64}Exchg16{$else}ExchgVariant{$endif}(pointer(P1), pointer(P2));
|
|
inc(P1, 16);
|
|
dec(P2, 16);
|
|
end;
|
|
end;
|
|
{$ifdef CPU64}
|
|
24:
|
|
begin
|
|
// optimized version for 64-bit TVariantDynArray and such
|
|
P2 := P1 + n * 24;
|
|
while P1 < P2 do
|
|
begin
|
|
ExchgVariant(Pointer(P1), Pointer(P2));
|
|
inc(P1, 24);
|
|
dec(P2, 24);
|
|
end;
|
|
end;
|
|
{$endif CPU64}
|
|
else
|
|
begin
|
|
// generic version
|
|
P2 := P1 + n * siz;
|
|
while P1 < P2 do
|
|
begin
|
|
Exchg(P1, P2, siz);
|
|
inc(P1, siz);
|
|
dec(P2, siz);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TDynArray.FillZero;
|
|
var
|
|
n: integer;
|
|
begin
|
|
n := GetCount;
|
|
if n <> 0 then
|
|
if not (rcfArrayItemManaged in fInfo.Flags) then
|
|
FillCharFast(fValue^^, n * fInfo.Cache.ItemSize, 0) // e.g. THash256
|
|
else
|
|
FillZeroRtti(fInfo.Cache.ItemInfo, fValue^^);
|
|
end;
|
|
|
|
procedure TDynArray.SaveTo(W: TBufferWriter);
|
|
begin
|
|
DynArraySave(pointer(fValue), fCountP, W, Info.Info);
|
|
end;
|
|
|
|
procedure TDynArray.SaveToStream(Stream: TStream);
|
|
var
|
|
W: TBufferWriter;
|
|
tmp: TTextWriterStackBuffer; // 8KB buffer
|
|
begin
|
|
if (fValue = nil) or
|
|
(Stream = nil) then
|
|
exit; // avoid GPF if void
|
|
W := TBufferWriter.Create(Stream, @tmp, SizeOf(tmp));
|
|
try
|
|
SaveTo(W);
|
|
W.Flush;
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end;
|
|
|
|
function TDynArray.SaveTo: RawByteString;
|
|
var
|
|
W: TRawByteStringStream;
|
|
begin
|
|
W := TRawByteStringStream.Create;
|
|
try
|
|
SaveToStream(W);
|
|
result := W.DataString;
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end;
|
|
|
|
function TDynArray.LoadFrom(Source, SourceMax: PAnsiChar): PAnsiChar;
|
|
var
|
|
read: TFastReader;
|
|
begin
|
|
{$ifndef PUREMORMOT2}
|
|
if SourceMax = nil then
|
|
// mORMot 1 unsafe backward compatible: assume fake 100MB Source input
|
|
SourceMax := Source + 100 shl 20;
|
|
{$endif PUREMORMOT2}
|
|
{%H-}read.Init(Source, SourceMax - Source);
|
|
LoadFromReader(read);
|
|
if read.P <> Source then
|
|
result := read.P
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
function TDynArray.LoadFromBinary(const Buffer: RawByteString): boolean;
|
|
var
|
|
read: TFastReader;
|
|
begin
|
|
read.Init(Buffer);
|
|
LoadFromReader(read);
|
|
result := read.P = read.Last;
|
|
end;
|
|
|
|
procedure TDynArray.LoadFromReader(var Read: TFastReader);
|
|
begin
|
|
if fValue <> nil then
|
|
begin
|
|
_BL_DynArray(pointer(fValue), Read, Info.Info);
|
|
if fCountP <> nil then // _BL_DynArray() set length -> reflect on Count
|
|
if fValue^ = nil then
|
|
fCountP^ := 0
|
|
else
|
|
fCountP^ := PDALen(PAnsiChar(fValue^) - _DALEN)^ + _DAOFF;
|
|
end;
|
|
end;
|
|
|
|
procedure TDynArray.LoadFromStream(Stream: TCustomMemoryStream);
|
|
var
|
|
S, P: PAnsiChar;
|
|
begin
|
|
S := PAnsiChar(Stream.Memory);
|
|
P := LoadFrom(S + Stream.Position, S + Stream.Size);
|
|
Stream.Seek(Int64(PtrUInt(P) - PtrUInt(S)), soBeginning);
|
|
end;
|
|
|
|
function TDynArray.SaveToJson(EnumSetsAsText: boolean; reformat: TTextWriterJsonFormat): RawUtf8;
|
|
begin
|
|
SaveToJson(result, EnumSetsAsText, reformat);
|
|
end;
|
|
|
|
procedure TDynArray.SaveToJson(out result: RawUtf8; EnumSetsAsText: boolean;
|
|
reformat: TTextWriterJsonFormat);
|
|
begin
|
|
SaveToJson(result, TEXTWRITEROPTIONS_ENUMASTEXT[EnumSetsAsText],
|
|
TEXTWRITEROBJECTOPTIONS_ENUMASTEXT[EnumSetsAsText], reformat);
|
|
end;
|
|
|
|
procedure TDynArray.SaveToJson(out result: RawUtf8; Options: TTextWriterOptions;
|
|
ObjectOptions: TTextWriterWriteObjectOptions; reformat: TTextWriterJsonFormat);
|
|
var
|
|
W: TTextWriter;
|
|
temp: TTextWriterStackBuffer;
|
|
begin
|
|
if GetCount = 0 then
|
|
result := '[]'
|
|
else
|
|
begin
|
|
W := DefaultJsonWriter.CreateOwnedStream(temp);
|
|
try
|
|
W.CustomOptions := W.CustomOptions + Options;
|
|
SaveToJson(W, ObjectOptions);
|
|
W.SetText(result, reformat);
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TDynArray.SaveToJson(W: TTextWriter;
|
|
ObjectOptions: TTextWriterWriteObjectOptions);
|
|
var
|
|
len, backup: PtrInt;
|
|
hacklen: PDALen;
|
|
begin
|
|
len := GetCount;
|
|
if len = 0 then
|
|
W.Add('[', ']')
|
|
else
|
|
begin
|
|
hacklen := PDALen(PAnsiChar(fValue^) - _DALEN);
|
|
backup := hacklen^;
|
|
try
|
|
hacklen^ := len - _DAOFF; // may use ExternalCount
|
|
W.AddTypedJson(fValue, Info.Info, ObjectOptions); // from mormot.core.json
|
|
finally
|
|
hacklen^ := backup;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure _GetDataFromJson(Data: pointer; var Json: PUtf8Char;
|
|
EndOfObject: PUtf8Char; Rtti: TRttiCustom;
|
|
CustomVariantOptions: PDocVariantOptions; Tolerant: boolean;
|
|
Interning: TRawUtf8InterningAbstract);
|
|
begin
|
|
raise ERttiException.Create('GetDataFromJson() not implemented - ' +
|
|
'please include mormot.core.json in your uses clause');
|
|
end;
|
|
|
|
function TDynArray.LoadFromJson(P: PUtf8Char; EndOfObject: PUtf8Char;
|
|
CustomVariantOptions: PDocVariantOptions; Tolerant: boolean;
|
|
Interning: TRawUtf8InterningAbstract): PUtf8Char;
|
|
begin
|
|
SetCount(0); // faster to use our own routine now
|
|
GetDataFromJson(fValue, P,
|
|
EndOfObject, Info, CustomVariantOptions, Tolerant, Interning);
|
|
if fCountP <> nil then
|
|
// GetDataFromJson() set the array length (capacity), not the external count
|
|
if fValue^ = nil then
|
|
fCountP^ := 0
|
|
else
|
|
fCountP^ := PDALen(PAnsiChar(fValue^) - _DALEN)^ + _DAOFF;
|
|
result := P;
|
|
end;
|
|
|
|
function TDynArray.LoadFromJson(const Json: RawUtf8;
|
|
CustomVariantOptions: PDocVariantOptions; Tolerant: boolean;
|
|
Interning: TRawUtf8InterningAbstract): boolean;
|
|
var
|
|
tmp: TSynTempBuffer;
|
|
begin
|
|
tmp.Init(Json);
|
|
try
|
|
result := LoadFromJson(tmp.buf, nil,
|
|
CustomVariantOptions, Tolerant, Interning) <> nil;
|
|
finally
|
|
tmp.Done;
|
|
end;
|
|
end;
|
|
|
|
function TDynArray.ItemCopyFirstField(Source, Dest: Pointer): boolean;
|
|
var
|
|
rtti: PRttiInfo;
|
|
begin
|
|
result := false;
|
|
if fInfo.ArrayFirstField in ptUnmanagedTypes then
|
|
MoveFast(Source^, Dest^, PT_SIZE[fInfo.ArrayFirstField])
|
|
else
|
|
begin
|
|
rtti := PT_INFO[fInfo.ArrayFirstField];
|
|
if rtti = nil then
|
|
exit; // ptNone, ptInterface, ptCustom
|
|
rtti^.Copy(Dest, Source);
|
|
end;
|
|
result := true;
|
|
end;
|
|
|
|
function BruteFind(P, V: PAnsiChar; cmp: TDynArraySortCompare; n, s: PtrInt): PtrInt;
|
|
begin // array is very small, or not sorted -> O(n) iterative search
|
|
result := 0;
|
|
repeat
|
|
if cmp(P^, V^) = 0 then
|
|
exit;
|
|
inc(result);
|
|
inc(P, s);
|
|
until result = n;
|
|
result := -1;
|
|
end;
|
|
|
|
function TDynArray.Find(const Item; const aIndex: TIntegerDynArray;
|
|
aCompare: TDynArraySortCompare): PtrInt;
|
|
var
|
|
n, L: PtrInt;
|
|
cmp: integer;
|
|
P: PAnsiChar;
|
|
begin
|
|
n := GetCount;
|
|
if Assigned(aCompare) and
|
|
(n > 0) then
|
|
begin
|
|
P := fValue^;
|
|
if length(aIndex) >= n then
|
|
begin // fast O(log(n)) binary search over aIndex[]
|
|
dec(n);
|
|
L := 0;
|
|
repeat
|
|
result := (L + n) shr 1;
|
|
cmp := aCompare(P[aIndex[result] * fInfo.Cache.ItemSize], Item);
|
|
if cmp = 0 then
|
|
begin
|
|
result := aIndex[result]; // returns index in TDynArray
|
|
exit;
|
|
end;
|
|
if cmp < 0 then
|
|
L := result + 1
|
|
else
|
|
n := result - 1;
|
|
until L > n;
|
|
end
|
|
else
|
|
begin // fallback to O(n) linear search on void aIndex[]
|
|
result := BruteFind(P, @Item, aCompare, n, fInfo.Cache.ItemSize);
|
|
exit;
|
|
end;
|
|
end;
|
|
result := -1;
|
|
end;
|
|
|
|
function SortFind(P, V: PAnsiChar; cmp: TDynArraySortCompare; R, s: PtrInt): PtrInt;
|
|
var
|
|
m, L: PtrInt;
|
|
res: integer;
|
|
begin // array is sorted -> use fast O(log(n)) binary search
|
|
L := 0;
|
|
dec(R);
|
|
repeat
|
|
result := (L + R) shr 1;
|
|
res := cmp(P[result * s], V^);
|
|
if res = 0 then
|
|
exit;
|
|
m := result - 1;
|
|
inc(result);
|
|
if res > 0 then // compile as cmovnle/cmovle opcodes on FPC x86_64
|
|
R := m
|
|
else
|
|
L := result;
|
|
until L > R;
|
|
result := -1;
|
|
end;
|
|
|
|
function TDynArray.Find(const Item; aCompare: TDynArraySortCompare): PtrInt;
|
|
var
|
|
n: PtrInt;
|
|
fnd: function(P, V: PAnsiChar; cmp: TDynArraySortCompare; n, s: PtrInt): PtrInt;
|
|
begin
|
|
n := GetCount;
|
|
if not Assigned(aCompare) then
|
|
aCompare := fCompare;
|
|
if n > 0 then
|
|
if Assigned(aCompare) then
|
|
begin
|
|
fnd := @BruteFind;
|
|
if n > 10 then
|
|
if fSorted and
|
|
(@aCompare = @fCompare) then
|
|
fnd := @SortFind
|
|
else if not(rcfArrayItemManaged in fInfo.Flags) and
|
|
(fInfo.ArrayRtti <> nil) and
|
|
(@aCompare = @PT_SORT[false, fInfo.ArrayRtti.Parser]) then
|
|
begin // optimized brute force search with potential SSE2 asm
|
|
result := AnyScanIndex(fValue^, @Item, n, fInfo.Cache.ItemSize);
|
|
exit;
|
|
end;
|
|
result := fnd(fValue^, @Item, aCompare, n, fInfo.Cache.ItemSize);
|
|
end
|
|
else
|
|
result := IndexOf(Item, {caseinsens=}false) // no fCompare -> default
|
|
else
|
|
result := -1;
|
|
end;
|
|
|
|
function TDynArray.FindIndex(const Item; aIndex: PIntegerDynArray;
|
|
aCompare: TDynArraySortCompare): PtrInt;
|
|
begin
|
|
if aIndex <> nil then
|
|
result := Find(Item, aIndex^, aCompare)
|
|
else
|
|
result := Find(Item, aCompare);
|
|
end;
|
|
|
|
function TDynArray.FindAndFill(var Item; aIndex: PIntegerDynArray;
|
|
aCompare: TDynArraySortCompare): integer;
|
|
begin
|
|
result := FindIndex(Item, aIndex, aCompare);
|
|
if result >= 0 then
|
|
// if found, fill Item with the matching item
|
|
ItemCopy(PAnsiChar(fValue^) + (result * fInfo.Cache.ItemSize), @Item);
|
|
end;
|
|
|
|
function TDynArray.FindAndDelete(const Item; aIndex: PIntegerDynArray;
|
|
aCompare: TDynArraySortCompare): integer;
|
|
begin
|
|
result := FindIndex(Item, aIndex, aCompare);
|
|
if result >= 0 then
|
|
// if found, delete the item from the array
|
|
Delete(result);
|
|
end;
|
|
|
|
function TDynArray.FindAndUpdate(const Item; aIndex: PIntegerDynArray;
|
|
aCompare: TDynArraySortCompare): integer;
|
|
begin
|
|
result := FindIndex(Item, aIndex, aCompare);
|
|
if result >= 0 then
|
|
// if found, fill Item with the matching item
|
|
ItemCopy(@Item, PAnsiChar(fValue^) + (result * fInfo.Cache.ItemSize));
|
|
end;
|
|
|
|
function TDynArray.FindAndAddIfNotExisting(const Item; aIndex: PIntegerDynArray;
|
|
aCompare: TDynArraySortCompare): integer;
|
|
begin
|
|
result := FindIndex(Item, aIndex, aCompare);
|
|
if result < 0 then
|
|
// -1 will mark success
|
|
Add(Item);
|
|
end;
|
|
|
|
function TDynArray.FindAllSorted(const Item;
|
|
out FirstIndex, LastIndex: integer): boolean;
|
|
var
|
|
found, last: integer; // FastLocateSorted() requires an integer
|
|
siz: PtrInt;
|
|
P, val: PAnsiChar;
|
|
begin
|
|
result := FastLocateSorted(Item, found);
|
|
if not result then
|
|
exit;
|
|
FirstIndex := found;
|
|
P := fValue^;
|
|
siz := fInfo.Cache.ItemSize;
|
|
inc(P, found * siz);
|
|
val := P; // faster than Item after RawUtf8 interning
|
|
while FirstIndex > 0 do
|
|
begin
|
|
dec(P, siz);
|
|
if fCompare(P^, val^) <> 0 then
|
|
break;
|
|
dec(FirstIndex);
|
|
end;
|
|
last := GetCount - 1;
|
|
LastIndex := found;
|
|
P := val;
|
|
while LastIndex < last do
|
|
begin
|
|
inc(P, siz);
|
|
if fCompare(P^, val^) <> 0 then
|
|
break;
|
|
inc(LastIndex);
|
|
end;
|
|
end;
|
|
|
|
function TDynArray.FindAllSorted(const Item; out FindCount: integer): pointer;
|
|
var
|
|
found: integer; // FastLocateSorted() requires an integer
|
|
siz: PtrInt;
|
|
P, fnd, limit: PAnsiChar;
|
|
begin
|
|
FindCount := 0;
|
|
result := nil;
|
|
if not FastLocateSorted(Item, found) then
|
|
exit;
|
|
P := fValue^;
|
|
limit := P;
|
|
siz := fInfo.Cache.ItemSize;
|
|
inc(P, found * siz);
|
|
fnd := P; // faster than Item after RawUtf8 interning
|
|
repeat
|
|
result := P;
|
|
inc(FindCount);
|
|
dec(P, siz);
|
|
until (P < limit) or
|
|
(fCompare(P^, fnd^) <> 0);
|
|
inc(limit, GetCount * siz);
|
|
P := fnd;
|
|
repeat
|
|
inc(P, siz);
|
|
if (P >= limit) or
|
|
(fCompare(P^, fnd^) <> 0) then
|
|
break;
|
|
inc(FindCount);
|
|
until false;
|
|
end;
|
|
|
|
function TDynArray.FastLocateSorted(const Item; out Index: integer): boolean;
|
|
var
|
|
n, i: PtrInt;
|
|
cmp: integer;
|
|
P: PAnsiChar;
|
|
begin
|
|
result := False;
|
|
n := GetCount;
|
|
if Assigned(fCompare) then
|
|
if n = 0 then // a void array is always sorted
|
|
Index := 0
|
|
else if fSorted then
|
|
begin
|
|
P := fValue^;
|
|
// first compare with the last sorted item (common case, e.g. with IDs)
|
|
dec(n);
|
|
cmp := fCompare(Item, P[n * fInfo.Cache.ItemSize]);
|
|
if cmp >= 0 then
|
|
begin
|
|
Index := n;
|
|
if cmp = 0 then
|
|
// was just added: returns true + index of last item
|
|
result := true
|
|
else
|
|
// bigger than last item: returns false + insert after last position
|
|
inc(Index);
|
|
exit;
|
|
end;
|
|
// O(log(n)) binary search of the sorted position
|
|
Index := 0; // more efficient code if we use Index and not a local var
|
|
repeat
|
|
i := (Index + n) shr 1;
|
|
cmp := fCompare(Item, P[i * fInfo.Cache.ItemSize]);
|
|
if cmp = 0 then
|
|
begin
|
|
// returns true + index of existing Item
|
|
Index := i;
|
|
result := True;
|
|
exit;
|
|
end
|
|
else if cmp > 0 then
|
|
Index := i + 1
|
|
else
|
|
n := i - 1;
|
|
until Index > n;
|
|
// Item not found: returns false + the index where to insert
|
|
end
|
|
else
|
|
Index := -1 // not Sorted
|
|
else
|
|
Index := -1; // no fCompare()
|
|
end;
|
|
|
|
procedure TDynArray.FastAddSorted(Index: PtrInt; const Item);
|
|
begin
|
|
Insert(Index, Item);
|
|
fSorted := true; // Insert -> SetCount -> fSorted := false
|
|
end;
|
|
|
|
procedure TDynArray.FastDeleteSorted(Index: PtrInt);
|
|
begin
|
|
Delete(Index);
|
|
fSorted := true; // Delete -> SetCount -> fSorted := false
|
|
end;
|
|
|
|
function TDynArray.FastLocateOrAddSorted(const Item; wasAdded: PBoolean): integer;
|
|
var
|
|
added: boolean;
|
|
begin
|
|
added := not FastLocateSorted(Item, result) and
|
|
(result >= 0);
|
|
if added then
|
|
begin
|
|
Insert(result, Item);
|
|
fSorted := true; // Insert -> SetCount -> fSorted := false
|
|
end;
|
|
if wasAdded <> nil then
|
|
wasAdded^ := added;
|
|
end;
|
|
|
|
type
|
|
// internal structure used to make QuickSort faster & with less stack usage
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TDynArrayQuickSort = record
|
|
{$else}
|
|
TDynArrayQuickSort = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
public
|
|
Compare: TDynArraySortCompare;
|
|
CompareEvent: TOnDynArraySortCompare;
|
|
Pivot: pointer;
|
|
index: PCardinalArray;
|
|
ElemSize: cardinal;
|
|
p: PtrInt;
|
|
Value: PAnsiChar;
|
|
IP, JP: PAnsiChar;
|
|
procedure QuickSort(L, R: PtrInt);
|
|
procedure QuickSortIndexed(L, R: PtrInt);
|
|
procedure QuickSortEvent(L, R: PtrInt);
|
|
procedure QuickSortEventReverse(L, R: PtrInt);
|
|
end;
|
|
|
|
procedure QuickSortIndexedPUtf8Char(Values: PPUtf8CharArray; Count: integer;
|
|
var SortedIndexes: TCardinalDynArray; CaseSensitive: boolean);
|
|
var
|
|
QS: TDynArrayQuickSort;
|
|
begin
|
|
if CaseSensitive then
|
|
QS.Compare := SortDynArrayPUtf8Char
|
|
else
|
|
QS.Compare := SortDynArrayPUtf8CharI;
|
|
QS.Value := pointer(Values);
|
|
QS.ElemSize := SizeOf(PUtf8Char);
|
|
SetLength(SortedIndexes, Count);
|
|
FillIncreasing(pointer(SortedIndexes), 0, Count);
|
|
QS.Index := pointer(SortedIndexes);
|
|
QS.QuickSortIndexed(0, Count - 1);
|
|
end;
|
|
|
|
procedure DynArraySortIndexed(Values: pointer; ItemSize, Count: integer;
|
|
out Indexes: TSynTempBuffer; Compare: TDynArraySortCompare);
|
|
begin
|
|
DynArraySortIndexed(Values, ItemSize, Count,
|
|
pointer(Indexes.InitIncreasing(Count)), Compare);
|
|
end;
|
|
|
|
procedure DynArraySortIndexed(Values: pointer; ItemSize, Count: integer;
|
|
Indexes: PCardinalArray; Compare: TDynArraySortCompare);
|
|
var
|
|
QS: TDynArrayQuickSort;
|
|
begin
|
|
QS.Compare := Compare;
|
|
QS.Value := Values;
|
|
QS.ElemSize := ItemSize;
|
|
QS.Index := Indexes;
|
|
QS.QuickSortIndexed(0, Count - 1);
|
|
end;
|
|
|
|
procedure TDynArrayQuickSort.QuickSort(L, R: PtrInt);
|
|
var
|
|
I, J: PtrInt;
|
|
begin
|
|
if L < R then
|
|
repeat
|
|
I := L;
|
|
J := R;
|
|
p := (L + R) shr 1;
|
|
repeat
|
|
Pivot := Value + PtrUInt(p) * ElemSize;
|
|
IP := Value + PtrUInt(I) * ElemSize;
|
|
JP := Value + PtrUInt(J) * ElemSize;
|
|
while Compare(IP^, Pivot^) < 0 do
|
|
begin
|
|
inc(I);
|
|
inc(IP, ElemSize);
|
|
end;
|
|
while Compare(JP^, Pivot^) > 0 do
|
|
begin
|
|
dec(J);
|
|
dec(JP, ElemSize);
|
|
end;
|
|
if I <= J then
|
|
begin
|
|
if I <> J then
|
|
Exchg(IP, JP, ElemSize);
|
|
if p = I then
|
|
p := J
|
|
else if p = J then
|
|
p := I;
|
|
inc(I);
|
|
dec(J);
|
|
end;
|
|
until I > J;
|
|
if J - L < R - I then
|
|
begin
|
|
// use recursion only for smaller range
|
|
if L < J then
|
|
QuickSort(L, J);
|
|
L := I;
|
|
end
|
|
else
|
|
begin
|
|
if I < R then
|
|
QuickSort(I, R);
|
|
R := J;
|
|
end;
|
|
until L >= R;
|
|
end;
|
|
|
|
procedure TDynArrayQuickSort.QuickSortEvent(L, R: PtrInt);
|
|
var
|
|
I, J: PtrInt;
|
|
begin
|
|
if L < R then
|
|
repeat
|
|
I := L;
|
|
J := R;
|
|
p := (L + R) shr 1;
|
|
repeat
|
|
Pivot := Value + PtrUInt(p) * ElemSize;
|
|
IP := Value + PtrUInt(I) * ElemSize;
|
|
JP := Value + PtrUInt(J) * ElemSize;
|
|
while CompareEvent(IP^, Pivot^) < 0 do
|
|
begin
|
|
inc(I);
|
|
inc(IP, ElemSize);
|
|
end;
|
|
while CompareEvent(JP^, Pivot^) > 0 do
|
|
begin
|
|
dec(J);
|
|
dec(JP, ElemSize);
|
|
end;
|
|
if I <= J then
|
|
begin
|
|
if I <> J then
|
|
Exchg(IP, JP, ElemSize);
|
|
if p = I then
|
|
p := J
|
|
else if p = J then
|
|
p := I;
|
|
inc(I);
|
|
dec(J);
|
|
end;
|
|
until I > J;
|
|
if J - L < R - I then
|
|
begin
|
|
// use recursion only for smaller range
|
|
if L < J then
|
|
QuickSortEvent(L, J);
|
|
L := I;
|
|
end
|
|
else
|
|
begin
|
|
if I < R then
|
|
QuickSortEvent(I, R);
|
|
R := J;
|
|
end;
|
|
until L >= R;
|
|
end;
|
|
|
|
procedure TDynArrayQuickSort.QuickSortEventReverse(L, R: PtrInt);
|
|
var
|
|
I, J: PtrInt;
|
|
begin
|
|
if L < R then
|
|
repeat
|
|
I := L;
|
|
J := R;
|
|
p := (L + R) shr 1;
|
|
repeat
|
|
Pivot := Value + PtrUInt(p) * ElemSize;
|
|
IP := Value + PtrUInt(I) * ElemSize;
|
|
JP := Value + PtrUInt(J) * ElemSize;
|
|
while CompareEvent(IP^, Pivot^) > 0 do
|
|
begin
|
|
inc(I);
|
|
inc(IP, ElemSize);
|
|
end;
|
|
while CompareEvent(JP^, Pivot^) < 0 do
|
|
begin
|
|
dec(J);
|
|
dec(JP, ElemSize);
|
|
end;
|
|
if I <= J then
|
|
begin
|
|
if I <> J then
|
|
Exchg(IP, JP, ElemSize);
|
|
if p = I then
|
|
p := J
|
|
else if p = J then
|
|
p := I;
|
|
inc(I);
|
|
dec(J);
|
|
end;
|
|
until I > J;
|
|
if J - L < R - I then
|
|
begin
|
|
// use recursion only for smaller range
|
|
if L < J then
|
|
QuickSortEventReverse(L, J);
|
|
L := I;
|
|
end
|
|
else
|
|
begin
|
|
if I < R then
|
|
QuickSortEventReverse(I, R);
|
|
R := J;
|
|
end;
|
|
until L >= R;
|
|
end;
|
|
|
|
procedure TDynArrayQuickSort.QuickSortIndexed(L, R: PtrInt);
|
|
var
|
|
I, J: PtrInt;
|
|
tmp: integer;
|
|
begin
|
|
if L < R then
|
|
repeat
|
|
I := L;
|
|
J := R;
|
|
p := (L + R) shr 1;
|
|
repeat
|
|
Pivot := Value + index[p] * ElemSize;
|
|
while Compare(Value[index[I] * ElemSize], Pivot^) < 0 do
|
|
inc(I);
|
|
while Compare(Value[index[J] * ElemSize], Pivot^) > 0 do
|
|
dec(J);
|
|
if I <= J then
|
|
begin
|
|
if I <> J then
|
|
begin
|
|
tmp := index[I];
|
|
index[I] := index[J];
|
|
index[J] := tmp;
|
|
end;
|
|
if p = I then
|
|
p := J
|
|
else if p = J then
|
|
p := I;
|
|
inc(I);
|
|
dec(J);
|
|
end;
|
|
until I > J;
|
|
if J - L < R - I then
|
|
begin
|
|
// use recursion only for smaller range
|
|
if L < J then
|
|
QuickSortIndexed(L, J);
|
|
L := I;
|
|
end
|
|
else
|
|
begin
|
|
if I < R then
|
|
QuickSortIndexed(I, R);
|
|
R := J;
|
|
end;
|
|
until L >= R;
|
|
end;
|
|
|
|
procedure TDynArray.Sort(aCompare: TDynArraySortCompare);
|
|
begin
|
|
SortRange(0, Count - 1, aCompare);
|
|
fSorted := true;
|
|
end;
|
|
|
|
procedure QuickSortPtr(L, R: PtrInt; Compare: TDynArraySortCompare; V: PPointerArray);
|
|
var
|
|
I, J, P: PtrInt;
|
|
tmp: pointer;
|
|
begin
|
|
if L < R then
|
|
repeat
|
|
I := L;
|
|
J := R;
|
|
P := (L + R) shr 1;
|
|
repeat
|
|
while Compare(V[I], V[P]) < 0 do
|
|
inc(I);
|
|
while Compare(V[J], V[P]) > 0 do
|
|
dec(J);
|
|
if I <= J then
|
|
begin
|
|
tmp := V[I];
|
|
V[I] := V[J];
|
|
V[J] := tmp;
|
|
if P = I then
|
|
P := J
|
|
else if P = J then
|
|
P := I;
|
|
inc(I);
|
|
dec(J);
|
|
end;
|
|
until I > J;
|
|
if J - L < R - I then
|
|
begin
|
|
// use recursion only for smaller range
|
|
if L < J then
|
|
QuickSortPtr(L, J, Compare, V);
|
|
L := I;
|
|
end
|
|
else
|
|
begin
|
|
if I < R then
|
|
QuickSortPtr(I, R, Compare, V);
|
|
R := J;
|
|
end;
|
|
until L >= R;
|
|
end;
|
|
|
|
procedure TDynArray.SortRange(aStart, aStop: integer;
|
|
aCompare: TDynArraySortCompare);
|
|
var
|
|
QuickSort: TDynArrayQuickSort;
|
|
begin
|
|
if aStop <= aStart then
|
|
exit; // nothing to sort
|
|
if Assigned(aCompare) then
|
|
QuickSort.Compare := aCompare
|
|
else
|
|
QuickSort.Compare := @fCompare;
|
|
if Assigned(QuickSort.Compare) and
|
|
(fValue <> nil) and
|
|
(fValue^ <> nil) then
|
|
begin
|
|
if fInfo.ArrayRtti <> nil then
|
|
case fInfo.ArrayRtti.Parser of
|
|
// call optimized sorting functions for most simple types
|
|
ptWord:
|
|
if @QuickSort.Compare = @SortDynArrayWord then
|
|
begin
|
|
QuickSortWord(fValue^, aStart, aStop);
|
|
exit;
|
|
end;
|
|
ptInteger:
|
|
if @QuickSort.Compare = @SortDynArrayInteger then
|
|
begin
|
|
QuickSortInteger(fValue^, aStart, aStop);
|
|
exit;
|
|
end;
|
|
ptInt64:
|
|
if @QuickSort.Compare = @SortDynArrayInt64 then
|
|
begin
|
|
QuickSortInt64(fValue^, aStart, aStop);
|
|
exit;
|
|
end;
|
|
ptQWord:
|
|
if @QuickSort.Compare = @SortDynArrayQWord then
|
|
begin
|
|
QuickSortQWord(fValue^, aStart, aStop);
|
|
exit;
|
|
end;
|
|
ptDouble:
|
|
if @QuickSort.Compare = @SortDynArrayDouble then
|
|
begin
|
|
QuickSortDouble(fValue^, aStart, aStop);
|
|
exit;
|
|
end;
|
|
end;
|
|
if fInfo.Cache.ItemSize = SizeOf(pointer) then
|
|
// dedicated function for pointers - e.g. strings or T*ObjArray
|
|
QuickSortPtr(aStart, aStop, QuickSort.Compare, fValue^)
|
|
else
|
|
begin
|
|
// generic process for any kind of array items
|
|
QuickSort.Value := fValue^;
|
|
QuickSort.ElemSize := fInfo.Cache.ItemSize;
|
|
QuickSort.QuickSort(aStart, aStop);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TDynArray.IsSorted(aCompare: TDynArraySortCompare): boolean;
|
|
var
|
|
n: integer;
|
|
siz: PtrInt;
|
|
p, prev: PAnsiChar;
|
|
begin
|
|
result := false;
|
|
n := GetCount;
|
|
if not Assigned(aCompare) then
|
|
aCompare := fCompare;
|
|
if (not Assigned(aCompare)) or
|
|
(n = 0) then
|
|
exit; // nothing to sort
|
|
siz := fInfo.Cache.ItemSize;
|
|
p := fValue^;
|
|
prev := p;
|
|
inc(p, siz);
|
|
dec(n);
|
|
if n <> 0 then
|
|
repeat
|
|
if aCompare(p^, prev^) < 0 then
|
|
exit;
|
|
prev := p;
|
|
inc(p, siz);
|
|
dec(n);
|
|
until n = 0;
|
|
result := true; // all items are sorted
|
|
end;
|
|
|
|
procedure TDynArray.EnsureSorted(aCompare: TDynArraySortCompare);
|
|
begin
|
|
if IsSorted(aCompare) then
|
|
fSorted := true
|
|
else
|
|
Sort(aCompare);
|
|
end;
|
|
|
|
procedure TDynArray.Sort(const aCompare: TOnDynArraySortCompare; aReverse: boolean);
|
|
var
|
|
QuickSort: TDynArrayQuickSort;
|
|
R: PtrInt;
|
|
begin
|
|
if (not Assigned(aCompare)) or
|
|
(fValue = nil) or
|
|
(fValue^ = nil) then
|
|
exit; // nothing to sort
|
|
QuickSort.CompareEvent := aCompare;
|
|
QuickSort.Value := fValue^;
|
|
QuickSort.ElemSize := fInfo.Cache.ItemSize;
|
|
R := Count - 1;
|
|
if aReverse then
|
|
QuickSort.QuickSortEventReverse(0, R)
|
|
else
|
|
QuickSort.QuickSortEvent(0, R);
|
|
end;
|
|
|
|
procedure TDynArray.CreateOrderedIndex(var aIndex: TIntegerDynArray;
|
|
aCompare: TDynArraySortCompare);
|
|
var
|
|
QuickSort: TDynArrayQuickSort;
|
|
n: integer;
|
|
begin
|
|
if Assigned(aCompare) then
|
|
QuickSort.Compare := aCompare
|
|
else
|
|
QuickSort.Compare := @fCompare;
|
|
if Assigned(QuickSort.Compare) and
|
|
(fValue <> nil) and
|
|
(fValue^ <> nil) then
|
|
begin
|
|
n := GetCount;
|
|
if length(aIndex) < n then
|
|
begin
|
|
SetLength(aIndex, n);
|
|
FillIncreasing(pointer(aIndex), 0, n);
|
|
end;
|
|
QuickSort.Value := fValue^;
|
|
QuickSort.ElemSize := fInfo.Cache.ItemSize;
|
|
QuickSort.Index := pointer(aIndex);
|
|
QuickSort.QuickSortIndexed(0, n - 1);
|
|
end;
|
|
end;
|
|
|
|
procedure TDynArray.CreateOrderedIndex(out aIndex: TSynTempBuffer;
|
|
aCompare: TDynArraySortCompare);
|
|
var
|
|
QuickSort: TDynArrayQuickSort;
|
|
n: integer;
|
|
begin
|
|
if Assigned(aCompare) then
|
|
QuickSort.Compare := aCompare
|
|
else
|
|
QuickSort.Compare := @fCompare;
|
|
if Assigned(QuickSort.Compare) and
|
|
(fValue <> nil) and
|
|
(fValue^ <> nil) then
|
|
begin
|
|
n := GetCount;
|
|
QuickSort.Value := fValue^;
|
|
QuickSort.ElemSize := fInfo.Cache.ItemSize;
|
|
QuickSort.Index := PCardinalArray(aIndex.InitIncreasing(n));
|
|
QuickSort.QuickSortIndexed(0, n - 1);
|
|
end
|
|
else
|
|
aIndex.buf := nil; // avoid GPF in aIndex.Done
|
|
end;
|
|
|
|
procedure TDynArray.CreateOrderedIndexAfterAdd(var aIndex: TIntegerDynArray;
|
|
aCompare: TDynArraySortCompare);
|
|
var
|
|
ndx: integer;
|
|
begin
|
|
ndx := GetCount - 1;
|
|
if ndx < 0 then
|
|
exit;
|
|
if aIndex <> nil then
|
|
begin
|
|
// whole FillIncreasing(aIndex[]) for first time
|
|
if ndx >= length(aIndex) then
|
|
SetLength(aIndex, NextGrow(ndx)); // grow aIndex[] if needed
|
|
aIndex[ndx] := ndx;
|
|
end;
|
|
CreateOrderedIndex(aIndex, aCompare);
|
|
end;
|
|
|
|
procedure TDynArray.InitFrom(aAnother: PDynArray; var aValue);
|
|
begin
|
|
self := aAnother^; // raw RTTI fields copy
|
|
fValue := @aValue; // points to the new value
|
|
fCountP := nil;
|
|
end;
|
|
|
|
procedure TDynArray.AddDynArray(aSource: PDynArray;
|
|
aStartIndex: integer; aCount: integer);
|
|
var
|
|
SourceCount: integer;
|
|
begin
|
|
if (aSource <> nil) and
|
|
(aSource^.fValue <> nil) and
|
|
(fInfo.Cache.ItemInfo = aSource^.Info.Cache.ItemInfo) then
|
|
begin
|
|
// check supplied aCount paramter with (external) Source.Count
|
|
SourceCount := aSource^.Count;
|
|
if (aCount < 0) or
|
|
(aCount > SourceCount) then
|
|
aCount := SourceCount;
|
|
// actually add the items
|
|
AddArray(aSource.fValue^, aStartIndex, aCount);
|
|
end;
|
|
end;
|
|
|
|
function TDynArray.Equals(B: PDynArray; IgnoreCompare, CaseSensitive: boolean): boolean;
|
|
begin
|
|
result := Compares(B, IgnoreCompare, CaseSensitive) = 0;
|
|
end;
|
|
|
|
function TDynArray.Compares(B: PDynArray; IgnoreCompare, CaseSensitive: boolean): integer;
|
|
var
|
|
i, n: integer;
|
|
s: PtrUInt;
|
|
P1, P2: PAnsiChar;
|
|
begin
|
|
n := GetCount;
|
|
result := n - B.Count;
|
|
if (result = 0) and
|
|
(n <> 0) then
|
|
if fInfo.Cache.ItemInfo <> B.Info.Cache.ItemInfo then
|
|
result := ComparePointer(fValue^, B.fValue^)
|
|
else if Assigned(fCompare) and
|
|
not ignorecompare then
|
|
begin
|
|
// use specified fCompare() function
|
|
P1 := fValue^;
|
|
P2 := B.fValue^;
|
|
s := fInfo.Cache.ItemSize;
|
|
for i := 1 to n do
|
|
begin
|
|
result := fCompare(P1^, P2^);
|
|
if result <> 0 then
|
|
exit;
|
|
inc(P1, s);
|
|
inc(P2, s);
|
|
end;
|
|
end
|
|
else if rcfObjArray in fInfo.Flags then
|
|
// T*ObjArray comparison of published properties
|
|
result := ObjectCompare(fValue^, B.fValue^, n, not CaseSensitive)
|
|
else if not(rcfArrayItemManaged in fInfo.Flags) then
|
|
// binary comparison with length (always CaseSensitive)
|
|
result := MemCmp(fValue^, B.fValue^, n * fInfo.Cache.ItemSize)
|
|
else
|
|
result := BinaryCompare(fValue^, B.fValue^, fInfo.Cache.ItemInfo, n,
|
|
not CaseSensitive);
|
|
end;
|
|
|
|
procedure TDynArray.Copy(Source: PDynArray; ObjArrayByRef: boolean);
|
|
begin
|
|
if (fValue = nil) or
|
|
(fInfo.Cache.ItemInfo <> Source.Info.Cache.ItemInfo) then
|
|
exit;
|
|
if not ObjArrayByRef and
|
|
(rcfObjArray in fInfo.Flags) then
|
|
LoadFromJson(pointer(Source.SaveToJson))
|
|
else
|
|
begin
|
|
DynArrayCopy(fValue, Source.fValue, fInfo.Info, Source.fCountP);
|
|
if fCountP <> nil then
|
|
fCountP^ := GetCapacity;
|
|
end;
|
|
end;
|
|
|
|
procedure TDynArray.CopyFrom(const Source; MaxItem: integer; ObjArrayByRef: boolean);
|
|
var
|
|
SourceDynArray: TDynArray;
|
|
begin
|
|
SourceDynArray.InitRtti(fInfo, pointer(@Source)^);
|
|
SourceDynArray.fCountP := @MaxItem; // would set Count=0 at Init()
|
|
Copy(@SourceDynArray, ObjArrayByRef);
|
|
end;
|
|
|
|
procedure TDynArray.CopyTo(out Dest; ObjArrayByRef: boolean);
|
|
var
|
|
DestDynArray: TDynArray;
|
|
begin
|
|
DestDynArray.InitRtti(fInfo, Dest);
|
|
DestDynArray.Copy(@self, ObjArrayByRef);
|
|
end;
|
|
|
|
function IndexFind(P, V: PAnsiChar; cmp: TRttiCompare; rtti: PRttiInfo; n: integer): PtrInt;
|
|
var
|
|
comp: integer;
|
|
begin
|
|
result := 0;
|
|
repeat
|
|
inc(P, cmp(P, V, rtti, comp));
|
|
if comp = 0 then
|
|
exit;
|
|
inc(result);
|
|
dec(n);
|
|
until n = 0;
|
|
result := -1;
|
|
end;
|
|
|
|
function TDynArray.IndexOf(const Item; CaseInSensitive: boolean): PtrInt;
|
|
var
|
|
rtti: PRttiInfo;
|
|
cmp: TRttiCompare;
|
|
n: PtrInt;
|
|
label
|
|
bin;
|
|
begin
|
|
n := GetCount;
|
|
if (n <> 0) and
|
|
(@Item <> nil) then
|
|
if not(rcfArrayItemManaged in fInfo.Flags) then
|
|
bin: result := AnyScanIndex(fValue^, @Item, n, fInfo.Cache.ItemSize)
|
|
else
|
|
begin
|
|
rtti := fInfo.Cache.ItemInfo;
|
|
if rtti = nil then
|
|
goto bin; // unmanaged items
|
|
cmp := RTTI_COMPARE[CaseInSensitive, rtti.Kind];
|
|
if Assigned(cmp) then
|
|
result := IndexFind(fValue^, @Item, cmp, rtti, n)
|
|
else
|
|
goto bin;
|
|
end
|
|
else
|
|
result := -1;
|
|
end;
|
|
|
|
procedure TDynArray.UseExternalCount(aCountPointer: PInteger);
|
|
begin
|
|
fCountP := aCountPointer;
|
|
end;
|
|
|
|
procedure TDynArray.Void;
|
|
begin
|
|
fValue := nil;
|
|
end;
|
|
|
|
function TDynArray.IsVoid: boolean;
|
|
begin
|
|
result := fValue = nil;
|
|
end;
|
|
|
|
procedure TDynArray.InternalSetLength(OldLength, NewLength: PtrUInt);
|
|
var
|
|
p: PDynArrayRec;
|
|
NeededSize, minLength: PtrUInt;
|
|
begin
|
|
// this method is faster than default System.DynArraySetLength() function
|
|
p := fValue^;
|
|
// check that new array length is not just a finalize in disguise
|
|
if NewLength = 0 then
|
|
begin
|
|
if p <> nil then
|
|
begin
|
|
// FastDynArrayClear() with ObjArray support
|
|
dec(p);
|
|
if (p^.refCnt > 0) and
|
|
DACntDecFree(p^.refCnt) then
|
|
begin
|
|
if (OldLength <> 0) and
|
|
not fNoFinalize then
|
|
if rcfArrayItemManaged in fInfo.Flags then
|
|
FastFinalizeArray(fValue^, fInfo.Cache.ItemInfo, OldLength)
|
|
else if rcfObjArray in fInfo.Flags then
|
|
RawObjectsClear(fValue^, OldLength);
|
|
FreeMem(p);
|
|
end;
|
|
fValue^ := nil;
|
|
end;
|
|
exit;
|
|
end;
|
|
// calculate the needed size of the resulting memory structure on heap
|
|
NeededSize := NewLength * PtrUInt(fInfo.Cache.ItemSize) + SizeOf(TDynArrayRec);
|
|
{$ifdef CPU32}
|
|
if NeededSize > 1 shl 30 then
|
|
// in practice, consider that max workable memory block is 1 GB on 32-bit
|
|
raise EDynArray.CreateUtf8('TDynArray.InternalSetLength(%,%) size concern',
|
|
[fInfo.Name, NewLength]);
|
|
{$endif CPU32}
|
|
// if not shared (refCnt=1), resize; if shared, create copy (not thread safe)
|
|
if p = nil then
|
|
begin
|
|
p := AllocMem(NeededSize); // RTL/OS will return zeroed memory
|
|
OldLength := NewLength; // no FillcharFast() below
|
|
end
|
|
else
|
|
begin
|
|
dec(p); // p^ = start of heap object
|
|
if p^.refCnt = 1 then
|
|
begin
|
|
// we own the dynamic array instance -> direct reallocation
|
|
if (NewLength < OldLength) and
|
|
not fNoFinalize then
|
|
// reduce array in-place
|
|
if rcfArrayItemManaged in fInfo.Flags then // in trailing items
|
|
FastFinalizeArray(pointer(PAnsiChar(p) + NeededSize),
|
|
fInfo.Cache.ItemInfo, OldLength - NewLength)
|
|
else if rcfObjArray in fInfo.Flags then // FreeAndNil() of resized objects
|
|
RawObjectsClear(pointer(PAnsiChar(p) + NeededSize), OldLength - NewLength);
|
|
ReallocMem(p, NeededSize);
|
|
end
|
|
else
|
|
begin
|
|
// dynamic array already referenced elsewhere -> create our own copy
|
|
minLength := OldLength;
|
|
if minLength > NewLength then
|
|
minLength := NewLength;
|
|
if fInfo.Cache.ItemInfo = nil then // unmanaged items
|
|
begin
|
|
GetMem(p, NeededSize);
|
|
MoveFast(fValue^^, PByteArray(p)[SizeOf(TDynArrayRec)],
|
|
minLength * PtrUInt(fInfo.Cache.ItemSize));
|
|
end
|
|
else
|
|
begin
|
|
p := AllocMem(NeededSize);
|
|
OldLength := NewLength; // no FillcharFast() below
|
|
CopySeveral(@PByteArray(p)[SizeOf(TDynArrayRec)], fValue^,
|
|
minLength, fInfo.Cache.ItemInfo, fInfo.Cache.ItemSize);
|
|
end;
|
|
// for thread safety, adjust the refcount after data copy
|
|
if fNoFinalize then
|
|
FastDynArrayClear(fValue, nil)
|
|
else // note: rcfObjArray should never appear with refcnt>1
|
|
FastDynArrayClear(fValue, fInfo.Cache.ItemInfo);
|
|
end;
|
|
end;
|
|
// set refCnt=1 and new length to the heap header
|
|
with p^ do
|
|
begin
|
|
refCnt := 1;
|
|
length := NewLength;
|
|
end;
|
|
inc(p); // start of dynamic aray items
|
|
fValue^ := p;
|
|
// reset new allocated items content to zero
|
|
if NewLength > OldLength then
|
|
begin
|
|
minLength := fInfo.Cache.ItemSize;
|
|
OldLength := OldLength * minLength;
|
|
FillCharFast(PAnsiChar(p)[OldLength], NewLength * minLength - OldLength, 0);
|
|
end;
|
|
end;
|
|
|
|
procedure TDynArray.SetCount(aCount: PtrInt);
|
|
const
|
|
MINIMUM_SIZE = 64;
|
|
var
|
|
oldlen, extcount, arrayptr, capa, delta: PtrInt;
|
|
begin
|
|
arrayptr := PtrInt(fValue);
|
|
extcount := PtrInt(fCountP);
|
|
fSorted := false;
|
|
if arrayptr = 0 then
|
|
exit; // avoid GPF if void
|
|
arrayptr := PPtrInt(arrayptr)^;
|
|
if extcount <> 0 then
|
|
begin
|
|
// fCountP^ as external capacity
|
|
oldlen := PInteger(extcount)^;
|
|
delta := aCount - oldlen;
|
|
if delta = 0 then
|
|
exit;
|
|
PInteger(extcount)^ := aCount; // store new length
|
|
if arrayptr <> 0 then
|
|
begin
|
|
// non void array: check new count against existing capacity
|
|
capa := PDALen(arrayptr - _DALEN)^ + _DAOFF;
|
|
if delta > 0 then
|
|
begin
|
|
// size-up - Add() - is handled branchless
|
|
if capa >= aCount then
|
|
exit; // no need to grow
|
|
capa := NextGrow(capa);
|
|
if capa > aCount then
|
|
aCount := capa; // grow by chunks
|
|
end
|
|
else
|
|
// size-down - Delete()
|
|
if (aCount > 0) and
|
|
((capa <= MINIMUM_SIZE) or
|
|
(capa - aCount < capa shr 3)) then
|
|
// reallocate memory only if worth it (for faster Delete)
|
|
exit;
|
|
end
|
|
else
|
|
begin
|
|
// void array
|
|
if (delta > 0) and
|
|
(aCount < MINIMUM_SIZE) then
|
|
// reserve some minimal (64) items for Add()
|
|
aCount := MINIMUM_SIZE;
|
|
end;
|
|
end
|
|
else
|
|
// no external capacity: use length()
|
|
if arrayptr = 0 then
|
|
oldlen := arrayptr
|
|
else
|
|
begin
|
|
oldlen := PDALen(arrayptr - _DALEN)^ + _DAOFF;
|
|
if oldlen = aCount then
|
|
exit; // InternalSetLength(samecount) would have made a private copy
|
|
end;
|
|
// no external Count, array size-down or array up-grow -> realloc
|
|
InternalSetLength(oldlen, aCount);
|
|
end;
|
|
|
|
procedure TDynArray.SetCapacity(aCapacity: PtrInt);
|
|
var
|
|
oldlen, capa: PtrInt;
|
|
begin
|
|
if fValue = nil then
|
|
exit;
|
|
capa := GetCapacity;
|
|
if fCountP <> nil then
|
|
begin
|
|
oldlen := fCountP^;
|
|
if oldlen > aCapacity then
|
|
fCountP^ := aCapacity;
|
|
end
|
|
else
|
|
oldlen := capa;
|
|
if capa <> aCapacity then
|
|
InternalSetLength(oldlen, aCapacity);
|
|
end;
|
|
|
|
procedure TDynArray.SetCompare(const aCompare: TDynArraySortCompare);
|
|
begin
|
|
if @aCompare <> @fCompare then
|
|
begin
|
|
@fCompare := @aCompare;
|
|
fSorted := false;
|
|
end;
|
|
end;
|
|
|
|
procedure TDynArray.Slice(var Dest; Limit, Offset: cardinal);
|
|
var
|
|
n: cardinal;
|
|
dst: TDynArray;
|
|
begin
|
|
if fValue = nil then
|
|
exit; // avoid GPF if void
|
|
n := GetCount;
|
|
if Offset >= n then
|
|
Limit := 0
|
|
else
|
|
begin
|
|
dec(n, Offset);
|
|
if Limit > n then
|
|
Limit := n;
|
|
end;
|
|
dst.InitRtti(fInfo, Dest);
|
|
dst.SetCapacity(Limit);
|
|
CopySeveral(pointer(Dest),
|
|
@(PByteArray(fValue^)[Offset * cardinal(fInfo.Cache.ItemSize)]),
|
|
Limit, fInfo.Cache.ItemInfo, fInfo.Cache.ItemSize);
|
|
end;
|
|
|
|
procedure TDynArray.SliceAsDynArray(Dest: PPointer; Offset, Limit: integer);
|
|
var
|
|
p: PDynArrayRec;
|
|
n: integer;
|
|
begin
|
|
if dest^ <> nil then
|
|
FastDynArrayClear(dest, fInfo.Cache.ItemInfo); // reset Dest variable slot
|
|
n := GetCount;
|
|
if Offset < 0 then
|
|
begin
|
|
// ! SliceAsDynArray(DA, -10); // last Count-10..Count-1 items
|
|
inc(Offset, n);
|
|
if Offset < 0 then
|
|
Offset := 0;
|
|
end;
|
|
if Offset >= n then // also handles n = 0
|
|
exit;
|
|
if (Offset = 0) and
|
|
((Limit = 0) or
|
|
(Limit >= n)) then
|
|
begin
|
|
// we can return the current dynamic array with proper Copy-On-Write
|
|
p := fValue^;
|
|
if p = nil then
|
|
exit;
|
|
dec(p);
|
|
inc(p^.refCnt); // COW reuse of the existing dynamic array instance
|
|
p^.Length := n; // no memory realloc/copy, just force Capacity=Length=Count
|
|
inc(p);
|
|
dest^ := p; // assign to Dest variable
|
|
end
|
|
else
|
|
begin
|
|
// ! SliceAsDynArray(DA, 0, 10); // first 0..9 items
|
|
// ! SliceAsDynArray(DA, 10, 20); // items 10..29 - truncated if Count < 30
|
|
if Limit = 0 then
|
|
// ! SliceAsDynArray(DA); // all items
|
|
// ! SliceAsDynArray(DA, 10); // all items excluding the first 0..9
|
|
Limit := n;
|
|
Slice(Dest^, Limit, Offset);
|
|
end;
|
|
end;
|
|
|
|
function TDynArray.AddArray(const DynArrayVar; aStartIndex, aCount: integer): integer;
|
|
var
|
|
c, s: PtrInt;
|
|
n: integer;
|
|
PS, PD: pointer;
|
|
begin
|
|
result := 0;
|
|
if fValue = nil then
|
|
exit; // avoid GPF if void
|
|
c := PtrInt(DynArrayVar);
|
|
if c <> 0 then
|
|
c := PDALen(c - _DALEN)^ + _DAOFF;
|
|
if aStartIndex >= c then
|
|
exit; // nothing to copy
|
|
if (aCount < 0) or
|
|
(cardinal(aStartIndex + aCount) > cardinal(c)) then
|
|
aCount := c - aStartIndex;
|
|
if aCount <= 0 then
|
|
exit;
|
|
result := aCount;
|
|
n := GetCount;
|
|
SetCount(n + aCount);
|
|
s := fInfo.Cache.ItemSize;
|
|
PS := PAnsiChar(DynArrayVar) + aStartIndex * s;
|
|
PD := PAnsiChar(fValue^) + n * s;
|
|
CopySeveral(PD, PS, aCount, fInfo.Cache.ItemInfo, s);
|
|
end;
|
|
|
|
function TDynArray.ItemLoadMem(Source, SourceMax: PAnsiChar): RawByteString;
|
|
begin
|
|
if (Source <> nil) and
|
|
(fInfo.Cache.ItemInfo = nil) then // unmanaged items
|
|
FastSetRawByteString(result, Source, fInfo.Cache.ItemSize)
|
|
else
|
|
begin
|
|
FastSetRawByteString(result, nil, fInfo.Cache.ItemSize);
|
|
FillCharFast(pointer(result)^, fInfo.Cache.ItemSize, 0);
|
|
ItemLoad(Source, pointer(result), SourceMax);
|
|
end;
|
|
end;
|
|
|
|
procedure TDynArray.ItemLoad(Source, SourceMax: PAnsiChar; Item: pointer);
|
|
begin
|
|
if Source <> nil then // avoid GPF
|
|
if fInfo.Cache.ItemInfo = nil then
|
|
begin
|
|
if {$ifndef PUREMORMOT2} (SourceMax = nil) or {$endif}
|
|
(Source + fInfo.Cache.ItemSize <= SourceMax) then
|
|
MoveFast(Source^, Item^, fInfo.Cache.ItemSize);
|
|
end
|
|
else
|
|
BinaryLoad(Item, Source, fInfo.Cache.ItemInfo, nil, SourceMax, rkAllTypes);
|
|
end;
|
|
|
|
procedure TDynArray.ItemLoadMemClear(var ItemTemp: RawByteString);
|
|
begin
|
|
ItemClear(pointer(ItemTemp));
|
|
ItemTemp := '';
|
|
end;
|
|
|
|
function TDynArray.ItemSave(Item: pointer): RawByteString;
|
|
begin
|
|
if fInfo.Cache.ItemInfo = nil then
|
|
FastSetRawByteString(result, Item, fInfo.Cache.ItemSize)
|
|
else
|
|
result := BinarySave(Item, fInfo.Cache.ItemInfo, rkAllTypes);
|
|
end;
|
|
|
|
function TDynArray.ItemLoadFind(Source, SourceMax: PAnsiChar): integer;
|
|
var
|
|
tmp: array[0..2047] of byte;
|
|
data: pointer;
|
|
begin
|
|
result := -1;
|
|
if (Source = nil) or
|
|
(fInfo.Cache.ItemSize > SizeOf(tmp)) then
|
|
exit;
|
|
if fInfo.Cache.ItemInfo = nil then // nil for unmanaged items
|
|
data := Source
|
|
else
|
|
begin
|
|
FillCharFast(tmp, fInfo.Cache.ItemSize, 0);
|
|
BinaryLoad(@tmp, Source, fInfo.Cache.ItemInfo, nil, SourceMax, rkAllTypes);
|
|
if Source = nil then
|
|
exit;
|
|
data := @tmp;
|
|
end;
|
|
try
|
|
if Assigned(fCompare) then
|
|
result := Find(data^) // use specific comparer
|
|
else
|
|
result := IndexOf(data^); // use RTTI
|
|
finally
|
|
if data = @tmp then
|
|
fInfo.ArrayRtti.ValueFinalize(data);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ ************ TDynArrayHasher }
|
|
|
|
function HashAnsiString(Item: PAnsiChar; Hasher: THasher): cardinal;
|
|
begin
|
|
Item := PPointer(Item)^; // passed by reference
|
|
if Item <> nil then
|
|
result := Hasher(0, Item, PStrLen(Item - _STRLEN)^)
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function HashAnsiStringI(Item: PUtf8Char; Hasher: THasher): cardinal;
|
|
var
|
|
tmp: array[byte] of AnsiChar; // avoid slow heap allocation
|
|
begin
|
|
Item := PPointer(Item)^;
|
|
if Item <> nil then
|
|
result := Hasher(0, tmp{%H-},
|
|
UpperCopy255Buf(tmp{%H-}, Item, PStrLen(Item - _STRLEN)^) - {%H-}tmp)
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function HashSynUnicode(Item: PSynUnicode; Hasher: THasher): cardinal;
|
|
begin
|
|
if PtrUInt(Item^) <> 0 then
|
|
result := Hasher(0, Pointer(Item^), Length(Item^) * 2)
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function HashSynUnicodeI(Item: PSynUnicode; Hasher: THasher): cardinal;
|
|
var
|
|
tmp: array[byte] of AnsiChar; // avoid slow heap allocation
|
|
begin
|
|
if PtrUInt(Item^) <> 0 then
|
|
result := Hasher(0, tmp{%H-}, UpperCopy255W(tmp{%H-}, Item^) - {%H-}tmp)
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function HashWideString(Item: PWideString; Hasher: THasher): cardinal;
|
|
begin
|
|
// WideString internal size is in bytes, not WideChar
|
|
if PtrUInt(Item^) <> 0 then
|
|
result := Hasher(0, Pointer(Item^), Length(Item^) * 2)
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function HashWideStringI(Item: PWideString; Hasher: THasher): cardinal;
|
|
var
|
|
tmp: array[byte] of AnsiChar; // avoid slow heap allocation
|
|
begin
|
|
if PtrUInt(Item^) <> 0 then
|
|
result := Hasher(0, tmp{%H-},
|
|
UpperCopy255W(tmp{%H-}, pointer(Item^), Length(Item^)) - {%H-}tmp)
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function HashPUtf8Char(Item: PAnsiChar; Hasher: THasher): cardinal;
|
|
begin
|
|
Item := PPointer(Item)^; // passed by reference
|
|
if Item <> nil then
|
|
result := Hasher(0, Item, StrLen(Item))
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function HashPUtf8CharI(Item: PUtf8Char; Hasher: THasher): cardinal;
|
|
var
|
|
tmp: array[byte] of AnsiChar; // avoid slow heap allocation
|
|
begin
|
|
Item := PPointer(Item)^;
|
|
if Item <> nil then
|
|
result := Hasher(0, tmp{%H-},
|
|
UpperCopy255Buf(tmp{%H-}, Item, StrLen(Item)) - {%H-}tmp)
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function HashByte(Item: pointer; Hasher: THasher): cardinal;
|
|
begin
|
|
result := Hasher(0, Item, SizeOf(byte));
|
|
end;
|
|
|
|
function HashWord(Item: pointer; Hasher: THasher): cardinal;
|
|
begin
|
|
result := Hasher(0, Item, SizeOf(word));
|
|
end;
|
|
|
|
function HashInteger(Item: pointer; Hasher: THasher): cardinal;
|
|
begin
|
|
result := Hasher(0, Item, SizeOf(integer));
|
|
end;
|
|
|
|
function HashInt64(Item: pointer; Hasher: THasher): cardinal;
|
|
begin
|
|
result := Hasher(0, Item, SizeOf(Int64));
|
|
end;
|
|
|
|
function HashExtended(Item: pointer; Hasher: THasher): cardinal;
|
|
begin
|
|
result := Hasher(0, Item, SizeOf(TSynExtended));
|
|
end;
|
|
|
|
function Hash128(Item: pointer; Hasher: THasher): cardinal;
|
|
begin
|
|
result := Hasher(0, Item, SizeOf(THash128));
|
|
end;
|
|
|
|
function Hash256(Item: pointer; Hasher: THasher): cardinal;
|
|
begin
|
|
result := Hasher(0, Item, SizeOf(THash256));
|
|
end;
|
|
|
|
function Hash512(Item: pointer; Hasher: THasher): cardinal;
|
|
begin
|
|
result := Hasher(0, Item, SizeOf(THash512));
|
|
end;
|
|
|
|
function VariantHash(const value: variant; CaseInsensitive: boolean;
|
|
Hasher: THasher): cardinal;
|
|
var
|
|
tmp: array[byte] of AnsiChar; // avoid heap allocation
|
|
vt: cardinal;
|
|
S: TStream;
|
|
W: TTextWriter;
|
|
P: pointer;
|
|
len: integer;
|
|
begin
|
|
if not Assigned(Hasher) then
|
|
Hasher := DefaultHasher;
|
|
with TVarData(value) do
|
|
begin
|
|
vt := VType;
|
|
P := @VByte; // same address than VWord/VInteger/VInt64...
|
|
case vt of
|
|
varNull, varEmpty:
|
|
len := 0; // good enough for void values
|
|
varShortInt, varByte:
|
|
len := 1;
|
|
varSmallint, varWord, varboolean:
|
|
len := 2;
|
|
varLongWord, varInteger, varSingle:
|
|
len := 4;
|
|
varInt64, varDouble, varDate, varCurrency, varWord64:
|
|
len := 8;
|
|
varString:
|
|
begin
|
|
len := length(RawUtf8(VAny));
|
|
P := VAny;
|
|
end;
|
|
varOleStr:
|
|
begin
|
|
len := length(WideString(VAny));
|
|
P := VAny;
|
|
end;
|
|
{$ifdef HASVARUSTRING}
|
|
varUString:
|
|
begin
|
|
len := length(UnicodeString(VAny));
|
|
P := VAny;
|
|
end;
|
|
{$endif HASVARUSTRING}
|
|
else
|
|
begin
|
|
S := TFakeWriterStream.Create;
|
|
W := DefaultJsonWriter.Create(S, @tmp, SizeOf(tmp));
|
|
try
|
|
W.AddVariant(value, twJsonEscape);
|
|
len := W.WrittenBytes;
|
|
if len > 255 then
|
|
len := 255;
|
|
P := @tmp; // big JSON won't be hasheable anyway -> use only buffer
|
|
finally
|
|
W.Free;
|
|
S.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
if CaseInsensitive and
|
|
(P <> @VByte) then
|
|
begin
|
|
len := UpperCopy255Buf(tmp, P, len) - tmp;
|
|
P := @tmp;
|
|
end;
|
|
result := Hasher(vt, P, len)
|
|
end;
|
|
end;
|
|
|
|
function HashVariant(Item: PVariant; Hasher: THasher): cardinal;
|
|
begin
|
|
result := VariantHash(Item^, false, Hasher);
|
|
end;
|
|
|
|
function HashVariantI(Item: PVariant; Hasher: THasher): cardinal;
|
|
begin
|
|
result := VariantHash(Item^, true, Hasher);
|
|
end;
|
|
|
|
const
|
|
// helper arrays to get the standard hash functions
|
|
PT_HASH: array[{caseinsensitive=}boolean, TRttiParserType] of pointer = (
|
|
// case sensitive hash functions:
|
|
(nil, // ptNone
|
|
nil, // ptArray
|
|
@HashByte, // ptBoolean
|
|
@HashByte, // ptByte
|
|
@HashInteger, // ptCardinal
|
|
@HashInt64, // ptCurrency
|
|
@HashInt64, // ptDouble
|
|
@HashExtended, // ptExtended
|
|
@HashInt64, // ptInt64
|
|
@HashInteger, // ptInteger
|
|
@HashInt64, // ptQWord
|
|
@HashAnsiString, // ptRawByteString
|
|
@HashAnsiString, // ptRawJson
|
|
@HashAnsiString, // ptRawUtf8
|
|
nil, // ptRecord
|
|
@HashInteger, // ptSingle
|
|
{$ifdef UNICODE} @HashSynUnicode {$else} @HashAnsiString {$endif}, // ptString
|
|
@HashSynUnicode, // ptSynUnicode
|
|
@HashInt64, // ptDateTime
|
|
@HashInt64, // ptDateTimeMS
|
|
@Hash128, // ptGuid
|
|
@Hash128, // ptHash128
|
|
@Hash256, // ptHash256
|
|
@Hash512, // ptHash512
|
|
@HashInt64, // ptOrm
|
|
@HashInt64, // ptTimeLog
|
|
@HashSynUnicode, // ptUnicodeString
|
|
@HashInt64, // ptUnixTime
|
|
@HashInt64, // ptUnixMSTime
|
|
@HashVariant, // ptVariant
|
|
@HashWideString, // ptWideString
|
|
@HashAnsiString, // ptWinAnsi
|
|
@HashWord, // ptWord
|
|
nil, // ptEnumeration
|
|
nil, // ptSet
|
|
{$ifdef CPU32} @HashInteger {$else} @HashInt64 {$endif}, // ptClass
|
|
nil, // ptDynArray
|
|
{$ifdef CPU32} @HashInteger {$else} @HashInt64 {$endif}, // ptInterface
|
|
@HashPUtf8Char, // ptPUtf8Char
|
|
nil), // ptCustom
|
|
// case insensitive hash functions:
|
|
(nil, // ptNone
|
|
nil, // ptArray
|
|
@HashByte, // ptBoolean
|
|
@HashByte, // ptByte
|
|
@HashInteger, // ptCardinal
|
|
@HashInt64, // ptCurrency
|
|
@HashInt64, // ptDouble
|
|
@HashExtended, // ptExtended
|
|
@HashInt64, // ptInt64
|
|
@HashInteger, // ptInteger
|
|
@HashInt64, // ptQWord
|
|
@HashAnsiString, // ptRawByteString
|
|
@HashAnsiStringI, // ptRawJson
|
|
@HashAnsiStringI, // ptRawUtf8
|
|
nil, // ptRecord
|
|
@HashInteger, // ptSingle
|
|
{$ifdef UNICODE} @HashSynUnicodeI {$else} @HashAnsiStringI {$endif}, // ptString
|
|
@HashSynUnicodeI, // ptSynUnicode
|
|
@HashInt64, // ptDateTime
|
|
@HashInt64, // ptDateTimeMS
|
|
@Hash128, // ptGuid
|
|
@Hash128, // ptHash128
|
|
@Hash256, // ptHash256
|
|
@Hash512, // ptHash512
|
|
@HashInt64, // ptOrm
|
|
@HashInt64, // ptTimeLog
|
|
@HashSynUnicodeI, // ptUnicodeString
|
|
@HashInt64, // ptUnixTime
|
|
@HashInt64, // ptUnixMSTime
|
|
@HashVariantI, // ptVariant
|
|
@HashWideStringI, // ptWideString
|
|
@HashAnsiStringI, // ptWinAnsi
|
|
@HashWord, // ptWord
|
|
nil, // ptEnumeration
|
|
nil, // ptSet
|
|
{$ifdef CPU32} @HashInteger {$else} @HashInt64 {$endif}, // ptClass
|
|
nil, // ptDynArray
|
|
{$ifdef CPU32} @HashInteger {$else} @HashInt64 {$endif}, // ptInterface
|
|
@HashPUtf8CharI, // ptPUtf8Char
|
|
nil)); // ptCustom
|
|
|
|
function DynArrayHashOne(Kind: TRttiParserType;
|
|
CaseInsensitive: boolean): TDynArrayHashOne;
|
|
begin
|
|
result := PT_HASH[CaseInsensitive, Kind];
|
|
end;
|
|
|
|
procedure TDynArrayHasher.Init(aDynArray: PDynArray; aHashItem: TDynArrayHashOne;
|
|
const aEventHash: TOnDynArrayHashOne; aHasher: THasher;
|
|
aCompare: TDynArraySortCompare; const aEventCompare: TOnDynArraySortCompare;
|
|
aCaseInsensitive: boolean);
|
|
begin
|
|
fDynArray := aDynArray;
|
|
fHashItem := aHashItem;
|
|
fEventHash := aEventHash;
|
|
if not (Assigned(fHashItem) or
|
|
Assigned(fEventHash)) then
|
|
begin
|
|
fHashItem := PT_HASH[aCaseInsensitive, fDynArray^.Info.ArrayFirstField];
|
|
if not Assigned(fHashItem) then
|
|
fEventHash := fDynArray^.Info.ValueFullHash;
|
|
end;
|
|
fCompare := aCompare;
|
|
fEventCompare := aEventCompare;
|
|
if not (Assigned(fCompare) or
|
|
Assigned(fEventCompare)) then
|
|
begin
|
|
fCompare := PT_SORT[aCaseInsensitive, fDynArray^.Info.ArrayFirstField];
|
|
if not Assigned(fCompare) then
|
|
fEventCompare := fDynArray^.Info.ValueFullCompare;
|
|
end;
|
|
HashTableInit(aHasher);
|
|
end;
|
|
|
|
procedure TDynArrayHasher.InitSpecific(aDynArray: PDynArray;
|
|
aKind: TRttiParserType; aCaseInsensitive: boolean; aHasher: THasher);
|
|
begin
|
|
fDynArray := aDynArray;
|
|
fHashItem := PT_HASH[aCaseInsensitive, aKind];
|
|
if Assigned(fHashItem) then
|
|
fEventHash := nil
|
|
else
|
|
fEventHash := aDynArray^.Info.ValueFullHash;
|
|
fCompare := PT_SORT[aCaseInsensitive, aKind];
|
|
if Assigned(fCompare) then
|
|
fEventCompare := nil
|
|
else
|
|
fEventCompare := aDynArray^.Info.ValueFullCompare;
|
|
HashTableInit(aHasher);
|
|
end;
|
|
|
|
procedure TDynArrayHasher.HashTableInit(aHasher: THasher);
|
|
begin
|
|
if not Assigned(aHasher) then
|
|
aHasher := DefaultHasher;
|
|
fHasher := aHasher;
|
|
fHashTableStore := nil;
|
|
if (Assigned(fHashItem) or
|
|
Assigned(fEventHash)) and
|
|
(Assigned(fCompare) or
|
|
Assigned(fEventCompare)) then
|
|
begin
|
|
// same logic than ReHash(true) with no data
|
|
fHashTableSize := 256;
|
|
{$ifdef DYNARRAYHASH_16BIT}
|
|
SetLength(fHashTableStore, 128 {$ifndef DYNARRAYHASH_PO2} + 1 {$endif});
|
|
fState := [hasHasher, hash16bit];
|
|
{$else}
|
|
SetLength(fHashTableStore, 256);
|
|
byte(State) := 1 shl ord(hasHasher)
|
|
{$endif DYNARRAYHASH_16BIT}
|
|
end
|
|
else
|
|
byte(fState) := 0;
|
|
{$ifdef DYNARRAYHASHCOLLISIONCOUNT}
|
|
CountCollisions := 0;
|
|
CountCollisionsCurrent := 0;
|
|
{$endif DYNARRAYHASHCOLLISIONCOUNT}
|
|
end;
|
|
|
|
procedure TDynArrayHasher.SetEventCompare(const Value: TOnDynArraySortCompare);
|
|
begin
|
|
if fDynArray^.GetCount <> 0 then
|
|
raise EDynArray.Create('TDynArrayHasher: unexpected SetEventCompare');
|
|
fEventCompare := Value;
|
|
HashTableInit(fHasher);
|
|
end;
|
|
|
|
procedure TDynArrayHasher.SetEventHash(const Value: TOnDynArrayHashOne);
|
|
begin
|
|
if fDynArray^.GetCount <> 0 then
|
|
raise EDynArray.Create('TDynArrayHasher: unexpected SetEventHash');
|
|
fEventHash := Value;
|
|
HashTableInit(fHasher);
|
|
end;
|
|
|
|
function TDynArrayHasher.HashOne(Item: pointer): cardinal;
|
|
begin
|
|
if Assigned(fEventHash) then
|
|
result := fEventHash(Item^)
|
|
else if not Assigned(fHashItem) then
|
|
result := 0 // will be ignored afterwards for sure
|
|
else
|
|
result := fHashItem(Item^, fHasher);
|
|
end;
|
|
|
|
function TDynArrayHasher.Equals(Item: pointer; ndx: PtrInt): boolean;
|
|
begin
|
|
ndx := ndx * fDynArray^.fInfo.Cache.ItemSize;
|
|
inc(ndx, PPtrInt(fDynArray^.Value)^);
|
|
if Assigned(fEventCompare) then
|
|
result := fEventCompare(pointer(ndx)^, Item^) = 0
|
|
else
|
|
result := fCompare(pointer(ndx)^, Item^) = 0;
|
|
end;
|
|
|
|
const
|
|
// reduces memory consumption and enhances distribution at hash table growing
|
|
_PRIMES: array[0..38 {$ifndef DYNARRAYHASH_PO2} + 13 {$endif}] of integer = (
|
|
{$ifndef DYNARRAYHASH_PO2}
|
|
251, 499, 797, 1259, 2011, 3203, 5087, 8089, 12853, 20399, 81649, 129607, 205759,
|
|
{$endif DYNARRAYHASH_PO2}
|
|
// start after HASH_PO2=2^18=262144 for DYNARRAYHASH_PO2 (poor 64-bit mul)
|
|
326617, 411527, 518509, 653267, 823117, 1037059, 1306601, 1646237,
|
|
2074129, 2613229, 3292489, 4148279, 5226491, 6584983, 8296553, 10453007,
|
|
13169977, 16593127, 20906033, 26339969, 33186281, 41812097, 52679969,
|
|
66372617, 83624237, 105359939, 132745199, 167248483, 210719881, 265490441,
|
|
334496971, 421439783, 530980861, 668993977, 842879579, 1061961721,
|
|
1337987929, 1685759167, 2123923447);
|
|
|
|
// as used internally by TDynArrayHasher.ForceReHash()
|
|
function NextPrime(v: integer): integer; {$ifdef HASINLINE}inline;{$endif}
|
|
var
|
|
i: PtrInt;
|
|
P: PIntegerArray;
|
|
begin
|
|
P := @_PRIMES;
|
|
for i := 0 to high(_PRIMES) do
|
|
begin
|
|
result := P^[i];
|
|
if result > v then
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
// see TTestCoreBase._TSynDictionary for some numbers, and why
|
|
// DYNARRAYHASH_LEMIRE + DYNARRAYHASH_PO2 are defined by default
|
|
function TDynArrayHasher.HashTableIndex(aHashCode: PtrUInt): PtrUInt;
|
|
begin
|
|
result := fHashTableSize;
|
|
{$ifdef DYNARRAYHASH_PO2}
|
|
// Delphi Win32 e.g. is not efficient with Lemire 64-bit multiplication
|
|
if result <= HASH_PO2 then
|
|
// efficient AND for power of two division
|
|
result := aHashCode and (result - 1)
|
|
else
|
|
{$endif DYNARRAYHASH_PO2}
|
|
{$ifdef DYNARRAYHASH_LEMIRE}
|
|
// FPC or dcc64 compile next line as very optimized asm
|
|
result := (QWord(aHashCode) * result) shr 32;
|
|
// https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction
|
|
{$else}
|
|
// regular 32-bit modulo over a Prime: slower but best from our tests
|
|
result := aHashCode mod result;
|
|
{$endif DYNARRAYHASH_LEMIRE}
|
|
end;
|
|
|
|
function TDynArrayHasher.HashTableIndexToIndex(aHashTableIndex: PtrInt): PtrInt;
|
|
begin
|
|
result := PtrUInt(fHashTableStore);
|
|
{$ifdef DYNARRAYHASH_16BIT}
|
|
if hash16bit in fState then
|
|
result := PWordArray(result)[aHashTableIndex]
|
|
else
|
|
{$endif DYNARRAYHASH_16BIT}
|
|
result := PIntegerArray(result)[aHashTableIndex];
|
|
end;
|
|
|
|
function TDynArrayHasher.Find(aHashCode: cardinal; aForAdd: boolean): PtrInt;
|
|
var
|
|
first, last, ndx, siz: PtrInt;
|
|
P: PAnsiChar;
|
|
begin
|
|
if not (hasHasher in fState) then
|
|
begin
|
|
result := -1;
|
|
exit;
|
|
end;
|
|
result := HashTableIndex(aHashCode);
|
|
first := result;
|
|
last := fHashTableSize;
|
|
P := fDynArray^.Value^;
|
|
siz := fDynArray^.Info.Cache.ItemSize;
|
|
repeat
|
|
ndx := HashTableIndexToIndex(result) - 1; // index+1 was stored
|
|
if ndx < 0 then
|
|
begin
|
|
// found void entry
|
|
result := -(result + 1);
|
|
exit;
|
|
end
|
|
else if not aForAdd and
|
|
(HashOne(P + ndx * siz) = aHashCode) then
|
|
begin
|
|
result := ndx;
|
|
exit;
|
|
end;
|
|
inc(result); // try next entry on hash collision
|
|
if result = last then
|
|
// reached the end -> search once from HashTable[0] to HashTable[first-1]
|
|
if result = first then
|
|
break
|
|
else
|
|
begin
|
|
result := 0;
|
|
last := first;
|
|
end;
|
|
until false;
|
|
RaiseFatalCollision('Find', aHashCode);
|
|
end;
|
|
|
|
function TDynArrayHasher.FindOrNew(aHashCode: cardinal; Item: pointer;
|
|
aHashTableIndex: PPtrInt): PtrInt;
|
|
var
|
|
first, last, ndx: PtrInt;
|
|
{$ifdef DYNARRAYHASHCOLLISIONCOUNT}
|
|
collisions: integer;
|
|
{$endif DYNARRAYHASHCOLLISIONCOUNT}
|
|
P: PAnsiChar;
|
|
begin
|
|
if not (hasHasher in fState) then
|
|
begin
|
|
result := -1;
|
|
exit; // we need comparison and hash functions
|
|
end;
|
|
{$ifdef DYNARRAYHASHCOLLISIONCOUNT}
|
|
collisions := 0;
|
|
{$endif DYNARRAYHASHCOLLISIONCOUNT}
|
|
result := HashTableIndex(aHashCode);
|
|
first := result;
|
|
last := fHashTableSize;
|
|
repeat
|
|
ndx := HashTableIndexToIndex(result) - 1; // index+1 was stored
|
|
if ndx < 0 then
|
|
begin
|
|
// not found: returns void index in HashTable[] as negative value
|
|
result := - (result + 1);
|
|
{$ifdef DYNARRAYHASHCOLLISIONCOUNT}
|
|
inc(CountCollisions, collisions);
|
|
inc(CountCollisionsCurrent, collisions);
|
|
{$endif DYNARRAYHASHCOLLISIONCOUNT}
|
|
exit;
|
|
end;
|
|
// comparison with item is faster than hash e.g. for huge strings
|
|
with fDynArray^ do
|
|
P := PAnsiChar(Value^) + ndx * fInfo.Cache.ItemSize;
|
|
if ((not Assigned(fEventCompare)) and
|
|
(fCompare(P^, Item^) = 0)) or
|
|
(Assigned(fEventCompare) and
|
|
(fEventCompare(P^, Item^) = 0)) then
|
|
begin
|
|
// found: returns the matching index
|
|
if aHashTableIndex <> nil then
|
|
aHashTableIndex^ := result;
|
|
result := ndx;
|
|
exit;
|
|
end;
|
|
// hash or slot collision -> search next item
|
|
{$ifdef DYNARRAYHASHCOLLISIONCOUNT}
|
|
inc(collisions);
|
|
{$endif DYNARRAYHASHCOLLISIONCOUNT}
|
|
inc(result);
|
|
if result = last then
|
|
// reached the end -> search once from HashTable[0] to HashTable[first-1]
|
|
if result = first then
|
|
break
|
|
else
|
|
begin
|
|
result := 0;
|
|
last := first;
|
|
end;
|
|
until false;
|
|
RaiseFatalCollision('FindOrNew', aHashCode);
|
|
end;
|
|
|
|
function TDynArrayHasher.FindOrNewComp(aHashCode: cardinal; Item: pointer;
|
|
Comp: TDynArraySortCompare): PtrInt;
|
|
var
|
|
first, last, ndx: PtrInt;
|
|
begin // cut-down version of FindOrNew()
|
|
if not Assigned(Comp) then
|
|
Comp := fCompare;
|
|
ndx := HashTableIndex(aHashCode);
|
|
first := ndx;
|
|
last := fHashTableSize;
|
|
if hasHasher in fState then
|
|
repeat
|
|
result := HashTableIndexToIndex(ndx) - 1; // index+1 was stored
|
|
if (result < 0) or // void slot = not found, or return matching index
|
|
(Comp((PAnsiChar(fDynArray^.Value^) +
|
|
result * fDynArray^.fInfo.Cache.ItemSize)^, Item^) = 0) then
|
|
exit;
|
|
inc(ndx); // hash or slot collision -> search next item
|
|
if ndx = last then
|
|
if ndx= first then
|
|
break
|
|
else
|
|
begin
|
|
ndx := 0;
|
|
last := first;
|
|
end;
|
|
until false;
|
|
result := 0; // make compiler happy
|
|
RaiseFatalCollision('FindOrNewComp', aHashCode);
|
|
end;
|
|
|
|
procedure TDynArrayHasher.HashAdd(aHashCode: cardinal; var result: PtrInt);
|
|
var
|
|
n, ndx: PtrInt;
|
|
begin
|
|
// on input: HashTable[result] slot is already computed
|
|
n := fDynArray^.Count;
|
|
ndx := result;
|
|
result := n;
|
|
if fHashTableSize < n then
|
|
RaiseFatalCollision('HashAdd HashTableSize', aHashCode);
|
|
if fHashTableSize - n < n shr 2 then
|
|
begin
|
|
// grow hash table when 25% void (192/256,384/512,768/1024,1536/2048...)
|
|
ForceReHash;
|
|
ndx := Find(aHashCode, {foradd=}true); // recompute position
|
|
if ndx >= 0 then
|
|
RaiseFatalCollision('HashAdd', aHashCode);
|
|
end;
|
|
ndx := -ndx - 1; // store Index+1 (0 means void slot)
|
|
inc(n);
|
|
{$ifdef DYNARRAYHASH_16BIT}
|
|
if hash16bit in fState then
|
|
PWordArray(fHashTableStore)[ndx] := n
|
|
else
|
|
{$endif DYNARRAYHASH_16BIT}
|
|
fHashTableStore[ndx] := n;
|
|
end; // on output: result holds the position in fValue[]
|
|
|
|
procedure TDynArrayHasher.HashDelete(aArrayIndex, aHashTableIndex: PtrInt;
|
|
aHashCode: cardinal);
|
|
var
|
|
first, next, last, n, s, ndx, i: PtrInt;
|
|
P: PAnsiChar;
|
|
indexes: array[0..511] of integer; // to be rehashed (seen always < 32)
|
|
begin
|
|
// retrieve hash table entries to be recomputed
|
|
first := aHashTableIndex;
|
|
last := fHashTableSize;
|
|
next := first;
|
|
n := 0;
|
|
repeat
|
|
{$ifdef DYNARRAYHASH_16BIT}
|
|
if hash16bit in fState then
|
|
PWordArray(fHashTableStore)[next] := 0
|
|
else
|
|
{$endif DYNARRAYHASH_16BIT}
|
|
fHashTableStore[next] := 0; // Clear slots
|
|
inc(next);
|
|
if next = last then
|
|
if next = first then
|
|
RaiseFatalCollision('HashDelete down', aHashCode)
|
|
else
|
|
begin
|
|
next := 0;
|
|
last := first;
|
|
end;
|
|
ndx := HashTableIndexToIndex(next) - 1; // index+1 was stored
|
|
if ndx < 0 then
|
|
break; // stop at void entry
|
|
if n = high(indexes) then // paranoid (typical 0..23 range)
|
|
RaiseFatalCollision('HashDelete indexes[] overflow', aHashCode);
|
|
indexes[n] := ndx;
|
|
inc(n);
|
|
until false;
|
|
// ReHash collided entries - note: item is not yet deleted in Value^[]
|
|
s := fDynArray^.Info.Cache.ItemSize;
|
|
for i := 0 to n - 1 do
|
|
begin
|
|
P := PAnsiChar(fDynArray^.Value^) + {%H-}indexes[i] * s;
|
|
ndx := FindOrNew(HashOne(P), P, nil);
|
|
if ndx < 0 then // ignore ndx>=0 dups (like ReHash)
|
|
begin
|
|
ndx := -ndx - 1; // compute the new slot position
|
|
n := indexes[i] + 1; // store index+1
|
|
{$ifdef DYNARRAYHASH_16BIT}
|
|
if hash16bit in fState then
|
|
PWordArray(fHashTableStore)[ndx] := n
|
|
else
|
|
{$endif DYNARRAYHASH_16BIT}
|
|
fHashTableStore[ndx] := n;
|
|
end;
|
|
end;
|
|
// adjust all stored indexes (using SSE2/AVX2 on x86_64)
|
|
{$ifdef DYNARRAYHASH_16BIT}
|
|
if hash16bit in fState then
|
|
DynArrayHashTableAdjust16(pointer(fHashTableStore), aArrayIndex, fHashTableSize)
|
|
else
|
|
{$endif DYNARRAYHASH_16BIT}
|
|
DynArrayHashTableAdjust(pointer(fHashTableStore), aArrayIndex, fHashTableSize);
|
|
end;
|
|
|
|
function TDynArrayHasher.FindBeforeAdd(Item: pointer; out wasAdded: boolean;
|
|
aHashCode: cardinal): PtrInt;
|
|
begin
|
|
wasAdded := false;
|
|
if hasHasher in fState then
|
|
begin
|
|
result := FindOrNew(aHashCode, Item, nil);
|
|
if result >= 0 then
|
|
exit;
|
|
// found no matching item
|
|
wasAdded := true;
|
|
HashAdd(aHashCode, result);
|
|
end
|
|
else
|
|
result := -1
|
|
end;
|
|
|
|
function TDynArrayHasher.FindBeforeDelete(Item: pointer): PtrInt;
|
|
var
|
|
h: cardinal;
|
|
ndx: PtrInt;
|
|
begin
|
|
if hasHasher in fState then
|
|
begin
|
|
h := HashOne(Item);
|
|
result := FindOrNew(h, Item, @ndx);
|
|
if result < 0 then
|
|
result := -1
|
|
else
|
|
HashDelete(result, ndx, h);
|
|
end
|
|
else
|
|
result := -1;
|
|
end;
|
|
|
|
procedure TDynArrayHasher.RaiseFatalCollision(const caller: shortstring;
|
|
aHashCode: cardinal);
|
|
begin
|
|
// a dedicated sub-procedure reduces code size
|
|
raise EDynArray.CreateUtf8('TDynArrayHasher.% fatal collision: ' +
|
|
'aHashCode=% HashTableSize=% Count=% Capacity=% Array=% Parser=%',
|
|
[caller, CardinalToHexShort(aHashCode), fHashTableSize, fDynArray^.Count,
|
|
fDynArray^.Capacity, fDynArray^.Info.Name, ToText(fDynArray^.Info.Parser)^]);
|
|
end;
|
|
|
|
function TDynArrayHasher.GetHashFromIndex(aIndex: PtrInt): cardinal;
|
|
var
|
|
P: pointer;
|
|
begin
|
|
P := fDynArray^.ItemPtr(aIndex);
|
|
if P <> nil then
|
|
result := HashOne(P)
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function TDynArrayHasher.Scan(Item: pointer): PtrInt;
|
|
var
|
|
P: PAnsiChar;
|
|
i, max, siz: PtrInt;
|
|
begin
|
|
result := -1;
|
|
max := fDynArray^.Count - 1;
|
|
P := fDynArray^.Value^;
|
|
siz := fDynArray^.Info.Cache.ItemSize;
|
|
if Assigned(fEventCompare) then // custom comparison
|
|
for i := 0 to max do
|
|
if fEventCompare(P^, Item^) = 0 then
|
|
begin
|
|
result := i;
|
|
break;
|
|
end
|
|
else
|
|
inc(P, siz)
|
|
else if Assigned(fCompare) then
|
|
for i := 0 to max do
|
|
if fCompare(P^, Item^) = 0 then
|
|
begin
|
|
result := i;
|
|
break;
|
|
end
|
|
else
|
|
inc(P, siz)
|
|
else
|
|
exit;
|
|
end;
|
|
|
|
function TDynArrayHasher.Find(Item: pointer): PtrInt;
|
|
begin
|
|
result := Find(Item, HashOne(Item));
|
|
end;
|
|
|
|
function TDynArrayHasher.Find(Item: pointer; aHashCode: cardinal): PtrInt;
|
|
begin
|
|
result := FindOrNew(aHashCode, Item, nil); // fallback to Scan() if needed
|
|
if result < 0 then
|
|
result := -1; // for coherency with most search methods
|
|
end;
|
|
|
|
type
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TFastReHash = record
|
|
{$else}
|
|
TFastReHash = object // dedicated object for better register allocation
|
|
{$endif USERECORDWITHMETHODS}
|
|
public
|
|
hc: cardinal;
|
|
{$ifdef DYNARRAYHASHCOLLISIONCOUNT}
|
|
collisions: integer;
|
|
{$endif DYNARRAYHASHCOLLISIONCOUNT}
|
|
ht: integer;
|
|
values, first, last, siz: PtrInt;
|
|
duplicates: PInteger;
|
|
P: PAnsiChar;
|
|
// fill fHashTableStore[] from all stored items
|
|
procedure Process(Hasher: PDynArrayHasher; count: PtrInt);
|
|
end;
|
|
|
|
procedure TFastReHash.Process(Hasher: PDynArrayHasher; count: PtrInt);
|
|
var
|
|
fnd, ndx: PtrInt;
|
|
label
|
|
s;
|
|
begin
|
|
// should match FindOrNew() logic
|
|
{$ifdef DYNARRAYHASHCOLLISIONCOUNT}
|
|
collisions := 0;
|
|
{$endif DYNARRAYHASHCOLLISIONCOUNT}
|
|
P := Hasher^.fDynArray^.Value^;
|
|
values := PtrUInt(P);
|
|
siz := Hasher^.fDynArray^.Info.Cache.ItemSize;
|
|
ht := 1; // store index + 1
|
|
repeat
|
|
s: if Assigned(Hasher^.fEventHash) then // inlined HashOne()
|
|
hc := Hasher^.fEventHash(P^)
|
|
else
|
|
hc := Hasher^.fHashItem(P^, Hasher^.fHasher);
|
|
ndx := Hasher^.HashTableIndex(hc);
|
|
first := ndx;
|
|
last := Hasher^.fHashTableSize;
|
|
repeat
|
|
{$ifdef DYNARRAYHASH_16BIT} // inlined HashTableIndexToIndex()
|
|
if hash16bit in Hasher^.fState then
|
|
begin
|
|
if PWordArray(Hasher^.fHashTableStore)[ndx] = 0 then // store index + 1
|
|
begin
|
|
// we can use this void entry (most common case)
|
|
PWordArray(Hasher^.fHashTableStore)[ndx] := ht;
|
|
inc(P, siz); // next item
|
|
inc(ht);
|
|
dec(count);
|
|
if count <> 0 then
|
|
goto s;
|
|
exit;
|
|
end;
|
|
end
|
|
else
|
|
{$endif DYNARRAYHASH_16BIT}
|
|
if Hasher^.fHashTableStore[ndx] = 0 then // void entry
|
|
begin
|
|
Hasher^.fHashTableStore[ndx] := ht;
|
|
inc(P, siz); // next item
|
|
inc(ht);
|
|
dec(count);
|
|
if count <> 0 then
|
|
goto s;
|
|
exit;
|
|
end;
|
|
{$ifdef DYNARRAYHASHCOLLISIONCOUNT}
|
|
inc(collisions);
|
|
{$endif DYNARRAYHASHCOLLISIONCOUNT}
|
|
if duplicates <> nil then
|
|
begin
|
|
// check for duplicated values only if necessary (slow down process)
|
|
if hash16bit in Hasher^.fState then
|
|
fnd := PWordArray(Hasher^.fHashTableStore)[ndx]
|
|
else
|
|
fnd := Hasher^.fHashTableStore[ndx];
|
|
fnd := values + (fnd - 1) * siz; // stored index + 1
|
|
if ((not Assigned(Hasher^.fEventCompare)) and
|
|
(Hasher^.fCompare(pointer(fnd)^, P^) = 0)) or
|
|
(Assigned(Hasher^.fEventCompare) and
|
|
(Hasher^.fEventCompare(pointer(fnd)^, P^) = 0)) then
|
|
begin
|
|
inc(duplicates^); // report but ignore duplicates
|
|
break;
|
|
end;
|
|
end;
|
|
inc(ndx);
|
|
if ndx = last then
|
|
// reached the end -> search from HashTable[0] to HashTable[first-1]
|
|
if ndx = first then
|
|
Hasher.RaiseFatalCollision('ReHash', hc)
|
|
else
|
|
begin
|
|
ndx := 0;
|
|
last := first;
|
|
end;
|
|
until false;
|
|
inc(P, siz); // next item
|
|
inc(ht);
|
|
dec(count);
|
|
until count = 0;
|
|
end;
|
|
|
|
procedure TDynArrayHasher.ForceReHash(duplicates: PInteger);
|
|
var
|
|
n, cap, siz: PtrInt;
|
|
fastrehash: TFastReHash;
|
|
begin
|
|
if duplicates <> nil then
|
|
duplicates^ := 0;
|
|
if not (hasHasher in fState) then
|
|
exit;
|
|
// Capacity better than Count or HashTableSize, * 2 to reserve some void slots
|
|
cap := fDynArray^.Capacity * 2;
|
|
{$ifdef DYNARRAYHASH_PO2}
|
|
if cap <= HASH_PO2 then
|
|
begin
|
|
siz := 256; // find nearest power of two for fast bitwise division
|
|
while siz < cap do
|
|
siz := siz shl 1;
|
|
end
|
|
else
|
|
{$endif DYNARRAYHASH_PO2}
|
|
siz := NextPrime(cap);
|
|
//QueryPerformanceMicroSeconds(t1); write('rehash count=',n,' old=',HashTableSize,
|
|
//' new=', siz, ' oldcol=',CountCollisionsCurrent);
|
|
fHashTableStore := nil;
|
|
fHashTableSize := siz;
|
|
{$ifdef DYNARRAYHASH_16BIT}
|
|
if siz <= 1 shl 16 then
|
|
begin
|
|
include(fState, hash16bit); // we can store indexes as 16-bit word values
|
|
siz := (siz shr 1) {$ifndef DYNARRAYHASH_PO2} + 1 {$endif}; // 32-bit count
|
|
end
|
|
else
|
|
exclude(fState, hash16bit);
|
|
{$endif DYNARRAYHASH_16BIT}
|
|
SetLength(fHashTableStore, siz); // fill with 0 (void slot)
|
|
{$ifdef DYNARRAYHASHCOLLISIONCOUNT}
|
|
CountCollisionsCurrent := 0; // count collision for this HashTable[] only
|
|
{$endif DYNARRAYHASHCOLLISIONCOUNT}
|
|
// fill fHashTableStore[]=index+1 from all existing items
|
|
n := fDynArray^.Count;
|
|
if n <> 0 then
|
|
begin
|
|
fastrehash.duplicates := duplicates;
|
|
fastrehash.Process(@self, n);
|
|
{$ifdef DYNARRAYHASHCOLLISIONCOUNT}
|
|
inc(CountCollisions, fastrehash.collisions);
|
|
inc(CountCollisionsCurrent, fastrehash.collisions);
|
|
{$endif DYNARRAYHASHCOLLISIONCOUNT}
|
|
end;
|
|
//QueryPerformanceMicroSeconds(t2); writeln(' newcol=',CountCollisionsCurrent,' ',
|
|
//(CountCollisionsCurrent * 100) div cardinal(n), '% ',MicroSecToString(t2-t1));
|
|
end;
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
function TDynArrayHasher.ReHash(forced: boolean): integer;
|
|
begin
|
|
ForceRehash(@result); // always forced for true thread-safety
|
|
end;
|
|
{$endif PUREMORMOT2}
|
|
|
|
|
|
{ ************ TDynArrayHashed }
|
|
|
|
{ TDynArrayHashed }
|
|
|
|
{$ifdef UNDIRECTDYNARRAY} // some Delphi 2009+ wrapper definitions
|
|
|
|
function TDynArrayHashed.GetCount: PtrInt;
|
|
begin
|
|
result := InternalDynArray.GetCount;
|
|
end;
|
|
|
|
procedure TDynArrayHashed.SetCount(aCount: PtrInt);
|
|
begin
|
|
InternalDynArray.SetCount(aCount);
|
|
end;
|
|
|
|
function TDynArrayHashed.GetCapacity: PtrInt;
|
|
begin
|
|
result := InternalDynArray.GetCapacity;
|
|
end;
|
|
|
|
procedure TDynArrayHashed.SetCapacity(aCapacity: PtrInt);
|
|
begin
|
|
InternalDynArray.SetCapacity(aCapacity);
|
|
end;
|
|
|
|
function TDynArrayHashed.Value: PPointer;
|
|
begin
|
|
result := InternalDynArray.fValue;
|
|
end;
|
|
|
|
function TDynArrayHashed.Info: TRttiCustom;
|
|
begin
|
|
result := InternalDynArray.fInfo;
|
|
end;
|
|
|
|
function TDynArrayHashed.ItemSize: PtrUInt;
|
|
begin
|
|
result := InternalDynArray.fInfo.Cache.ItemSize;
|
|
end;
|
|
|
|
procedure TDynArrayHashed.ItemCopy(Source, Dest: pointer);
|
|
begin
|
|
InternalDynArray.ItemCopy(Source, Dest);
|
|
end;
|
|
|
|
function TDynArrayHashed.ItemPtr(index: PtrInt): pointer;
|
|
begin
|
|
result := InternalDynArray.ItemPtr(index);
|
|
end;
|
|
|
|
function TDynArrayHashed.ItemCopyAt(index: PtrInt; Dest: pointer): boolean;
|
|
begin
|
|
result := InternalDynArray.ItemCopyAt(index, Dest);
|
|
end;
|
|
|
|
procedure TDynArrayHashed.Clear;
|
|
begin
|
|
InternalDynArray.SetCount(0);
|
|
end;
|
|
|
|
function TDynArrayHashed.Add(const Item): PtrInt;
|
|
begin
|
|
result := InternalDynArray.Add(Item);
|
|
end;
|
|
|
|
procedure TDynArrayHashed.Delete(aIndex: PtrInt);
|
|
begin
|
|
InternalDynArray.Delete(aIndex);
|
|
end;
|
|
|
|
function TDynArrayHashed.SaveTo: RawByteString;
|
|
begin
|
|
result := InternalDynArray.SaveTo;
|
|
end;
|
|
|
|
function TDynArrayHashed.LoadFrom(Source, SourceMax: PAnsiChar): PAnsiChar;
|
|
begin
|
|
result := InternalDynArray.LoadFrom(Source, SourceMax);
|
|
end;
|
|
|
|
function TDynArrayHashed.LoadFromBinary(const Buffer: RawByteString): boolean;
|
|
begin
|
|
result := InternalDynArray.LoadFromBinary(Buffer);
|
|
end;
|
|
|
|
procedure TDynArrayHashed.SaveTo(W: TBufferWriter);
|
|
begin
|
|
InternalDynArray.SaveTo(W);
|
|
end;
|
|
|
|
procedure TDynArrayHashed.Sort(aCompare: TDynArraySortCompare);
|
|
begin
|
|
InternalDynArray.Sort(aCompare);
|
|
end;
|
|
|
|
procedure TDynArrayHashed.CreateOrderedIndex(var aIndex: TIntegerDynArray;
|
|
aCompare: TDynArraySortCompare);
|
|
begin
|
|
InternalDynArray.CreateOrderedIndex(aIndex, aCompare);
|
|
end;
|
|
|
|
function TDynArrayHashed.SaveToJson(EnumSetsAsText: boolean;
|
|
reformat: TTextWriterJsonFormat): RawUtf8;
|
|
begin
|
|
result := InternalDynArray.SaveToJson(EnumSetsAsText, reformat);
|
|
end;
|
|
|
|
procedure TDynArrayHashed.SaveToJson(out result: RawUtf8; EnumSetsAsText: boolean;
|
|
reformat: TTextWriterJsonFormat);
|
|
begin
|
|
InternalDynArray.SaveToJson(result, EnumSetsAsText, reformat);
|
|
end;
|
|
|
|
procedure TDynArrayHashed.SaveToJson(W: TTextWriter);
|
|
begin
|
|
InternalDynArray.SaveToJson(W);
|
|
end;
|
|
|
|
function TDynArrayHashed.LoadFromJson(P: PUtf8Char; aEndOfObject: PUtf8Char;
|
|
CustomVariantOptions: PDocVariantOptions): PUtf8Char;
|
|
begin
|
|
result := InternalDynArray.LoadFromJson(P, aEndOfObject, CustomVariantOptions);
|
|
end;
|
|
|
|
{$endif UNDIRECTDYNARRAY}
|
|
|
|
procedure TDynArrayHashed.Init(aTypeInfo: PRttiInfo; var aValue;
|
|
aHashItem: TDynArrayHashOne; aCompare: TDynArraySortCompare;
|
|
aHasher: THasher; aCountPointer: PInteger; aCaseInsensitive: boolean);
|
|
begin
|
|
InitRtti(Rtti.RegisterType(aTypeInfo), aValue, aHashItem, aCompare,
|
|
aHasher, aCountPointer, aCaseInsensitive);
|
|
end;
|
|
|
|
procedure TDynArrayHashed.InitRtti(aRtti: TRttiCustom; var aValue;
|
|
aHashItem: TDynArrayHashOne; aCompare: TDynArraySortCompare;
|
|
aHasher: THasher; aCountPointer: PInteger; aCaseInsensitive: boolean);
|
|
begin
|
|
{$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$else}inherited{$endif}
|
|
InitRtti(aRtti, aValue, aCountPointer);
|
|
fHash.Init(@self, aHashItem, nil, aHasher, aCompare, nil, aCaseInsensitive);
|
|
{$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare := fHash.fCompare;
|
|
end;
|
|
|
|
procedure TDynArrayHashed.InitSpecific(aTypeInfo: PRttiInfo; var aValue;
|
|
aKind: TRttiParserType; aCountPointer: PInteger; aCaseInsensitive: boolean;
|
|
aHasher: THasher);
|
|
begin
|
|
{$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$else}inherited{$endif}
|
|
Init(aTypeInfo, aValue, aCountPointer);
|
|
fHash.InitSpecific(@self, aKind, aCaseInsensitive, aHasher);
|
|
{$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare := fHash.fCompare;
|
|
end;
|
|
|
|
function TDynArrayHashed.Scan(const Item): PtrInt;
|
|
begin
|
|
result := fHash.Scan(@Item);
|
|
end;
|
|
|
|
function TDynArrayHashed.FindHashed(const Item): PtrInt;
|
|
begin
|
|
result := fHash.FindOrNew(fHash.HashOne(@Item), @Item, nil);
|
|
if result < 0 then
|
|
result := -1; // for coherency with most methods
|
|
end;
|
|
|
|
function TDynArrayHashed.FindFromHash(const Item; aHashCode: cardinal): PtrInt;
|
|
begin
|
|
// overload FindHashed() trigger F2084 Internal Error: C2130 on Delphi XE3
|
|
result := fHash.FindOrNew(aHashCode, @Item, nil); // fallback to Scan() if needed
|
|
if result < 0 then
|
|
result := -1; // for coherency with most methods
|
|
end;
|
|
|
|
function TDynArrayHashed.FindHashedForAdding(const Item; out wasAdded: boolean;
|
|
noAddEntry: boolean): PtrInt;
|
|
begin
|
|
result := FindHashedForAdding(Item, wasAdded, fHash.HashOne(@Item), noAddEntry);
|
|
end;
|
|
|
|
function TDynArrayHashed.FindHashedForAdding(const Item; out wasAdded: boolean;
|
|
aHashCode: cardinal; noAddEntry: boolean): PtrInt;
|
|
begin
|
|
result := fHash.FindBeforeAdd(@Item, wasAdded, aHashCode);
|
|
if wasAdded and
|
|
not noAddEntry then
|
|
SetCount(result + 1); // reserve space for a void element in array
|
|
end;
|
|
|
|
function TDynArrayHashed.AddAndMakeUniqueName(aName: RawUtf8): pointer;
|
|
var
|
|
ndx: PtrInt;
|
|
j: PtrUInt;
|
|
added: boolean;
|
|
aName_: RawUtf8;
|
|
begin
|
|
if aName = '' then
|
|
aName := '_';
|
|
ndx := FindHashedForAdding(aName, added);
|
|
if not added then
|
|
begin
|
|
// force unique column name
|
|
aName_ := aName + '_';
|
|
j := 1;
|
|
repeat
|
|
if j > high(SmallUInt32Utf8) then // should never happen - 999 is enough
|
|
raise EDynArray.Create('TDynArrayHashed.AddAndMakeUniqueName overflow');
|
|
aName := aName_ + SmallUInt32Utf8[j];
|
|
ndx := FindHashedForAdding(aName, added);
|
|
inc(j);
|
|
until added;
|
|
end;
|
|
result := PAnsiChar(Value^) + ndx * Info.Cache.ItemSize;
|
|
PRawUtf8(result)^ := aName; // store unique name at 1st position
|
|
end;
|
|
|
|
function TDynArrayHashed.AddUniqueName(const aName: RawUtf8;
|
|
aNewIndex: PPtrInt): pointer;
|
|
begin
|
|
result := AddUniqueName(aName, '', [], aNewIndex);
|
|
end;
|
|
|
|
function TDynArrayHashed.AddUniqueName(const aName: RawUtf8; const ExceptionMsg: RawUtf8;
|
|
const ExceptionArgs: array of const; aNewIndex: PPtrInt): pointer;
|
|
var
|
|
ndx: PtrInt;
|
|
added: boolean;
|
|
begin
|
|
ndx := FindHashedForAdding(aName, added);
|
|
if added then
|
|
begin
|
|
if aNewIndex <> nil then
|
|
aNewIndex^ := ndx;
|
|
result := PAnsiChar(Value^) + ndx * Info.Cache.ItemSize;
|
|
PRawUtf8(result)^ := aName; // store unique name at 1st position
|
|
end
|
|
else if ExceptionMsg = '' then
|
|
raise EDynArray.CreateUtf8('TDynArrayHashed: Duplicated [%] name', [aName])
|
|
else
|
|
raise EDynArray.CreateUtf8(ExceptionMsg, ExceptionArgs);
|
|
end;
|
|
|
|
function TDynArrayHashed.FindHashedAndFill(var ItemToFill): PtrInt;
|
|
begin
|
|
result := fHash.FindOrNew(fHash.HashOne(@ItemToFill), @ItemToFill, nil);
|
|
if result < 0 then
|
|
result := -1
|
|
else
|
|
ItemCopy(PAnsiChar(Value^) + result * Info.Cache.ItemSize, @ItemToFill);
|
|
end;
|
|
|
|
function TDynArrayHashed.FindHashedAndUpdate(const Item;
|
|
AddIfNotExisting: boolean): PtrInt;
|
|
var
|
|
hc: cardinal;
|
|
begin
|
|
if hasHasher in fHash.fState then
|
|
begin
|
|
hc := fHash.HashOne(@Item);
|
|
result := fHash.FindOrNew(hc, @Item, nil);
|
|
if (result < 0) and
|
|
AddIfNotExisting then
|
|
begin
|
|
fHash.HashAdd(hc, result); // ReHash only if necessary
|
|
SetCount(result + 1); // add new item
|
|
end;
|
|
end
|
|
else
|
|
result := -1;
|
|
if result >= 0 then // update
|
|
ItemCopy(@Item, PAnsiChar(Value^) + result * Info.Cache.ItemSize);
|
|
end;
|
|
|
|
function TDynArrayHashed.FindHashedAndDelete(const Item; FillDeleted: pointer;
|
|
noDeleteEntry: boolean): PtrInt;
|
|
begin
|
|
result := fHash.FindBeforeDelete(@Item);
|
|
if result >= 0 then
|
|
begin
|
|
if FillDeleted <> nil then
|
|
ItemCopyAt(result, FillDeleted);
|
|
if not noDeleteEntry then
|
|
Delete(result);
|
|
end;
|
|
end;
|
|
|
|
function TDynArrayHashed.GetHashFromIndex(aIndex: PtrInt): cardinal;
|
|
begin
|
|
result := fHash.GetHashFromIndex(aIndex);
|
|
end;
|
|
|
|
procedure TDynArrayHashed.ForceReHash;
|
|
begin
|
|
fHash.ForceReHash;
|
|
end;
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
function TDynArrayHashed.ReHash(forced: boolean): integer;
|
|
begin
|
|
fHash.ForceReHash(@result); // always forced
|
|
end;
|
|
{$endif PUREMORMOT2}
|
|
|
|
procedure TDynArrayHashed.SetEventCompare(const cmp: TOnDynArraySortCompare);
|
|
begin
|
|
fHash.SetEventCompare(cmp);
|
|
end;
|
|
|
|
procedure TDynArrayHashed.SetEventHash(const hsh: TOnDynArrayHashOne);
|
|
begin
|
|
fHash.SetEventHash(hsh);
|
|
end;
|
|
|
|
|
|
function DynArray(aTypeInfo: PRttiInfo; var aValue; aCountPointer: PInteger): TDynArray;
|
|
begin
|
|
result.Init(aTypeInfo, aValue, aCountPointer);
|
|
end;
|
|
|
|
|
|
|
|
{ *************** Integer Arrays Extended Process }
|
|
|
|
procedure Exchg32(var A, B: integer); {$ifdef HASINLINE}inline;{$endif}
|
|
var
|
|
tmp: integer;
|
|
begin
|
|
tmp := A;
|
|
A := B;
|
|
B := tmp;
|
|
end;
|
|
|
|
function MedianQuickSelectInteger(Values: PIntegerArray; n: integer): integer;
|
|
var
|
|
low, high, median, middle, ll, hh: PtrInt;
|
|
begin
|
|
if n = 0 then
|
|
begin
|
|
result := 0;
|
|
exit;
|
|
end;
|
|
if n = 1 then
|
|
begin
|
|
result := Values[0];
|
|
exit;
|
|
end;
|
|
low := 0;
|
|
high := n - 1;
|
|
median := high shr 1;
|
|
repeat
|
|
if high <= low then
|
|
begin
|
|
// one item left
|
|
result := Values[median];
|
|
exit;
|
|
end;
|
|
if high = low + 1 then
|
|
begin
|
|
// two items -> return the smallest (not average)
|
|
if Values[low] > Values[high] then
|
|
Exchg32(Values[low], Values[high]);
|
|
result := Values[median];
|
|
exit;
|
|
end;
|
|
// find median of low, middle and high items; swap into position low
|
|
middle := (low + high) shr 1;
|
|
if Values[middle] > Values[high] then
|
|
Exchg32(Values[middle], Values[high]);
|
|
if Values[low] > Values[high] then
|
|
Exchg32(Values[low], Values[high]);
|
|
if Values[middle] > Values[low] then
|
|
Exchg32(Values[middle], Values[low]);
|
|
// swap low item (now in position middle) into position (low+1)
|
|
Exchg32(Values[middle], Values[low + 1]);
|
|
// nibble from each end towards middle, swapping items when stuck
|
|
ll := low + 1;
|
|
hh := high;
|
|
repeat
|
|
repeat
|
|
inc(ll);
|
|
until not (Values[low] > Values[ll]);
|
|
repeat
|
|
dec(hh);
|
|
until not (Values[hh] > Values[low]);
|
|
if hh < ll then
|
|
break;
|
|
Exchg32(Values[ll], Values[hh]);
|
|
until false;
|
|
// swap middle item (in position low) back into correct position
|
|
Exchg32(Values[low], Values[hh]);
|
|
// next active partition
|
|
if hh <= median then
|
|
low := ll;
|
|
if hh >= median then
|
|
high := hh - 1;
|
|
until false;
|
|
end;
|
|
|
|
function MedianQuickSelect(const OnCompare: TOnValueGreater; n: integer;
|
|
var TempBuffer: TSynTempBuffer): integer;
|
|
var
|
|
low, high, middle, median, ll, hh: PtrInt;
|
|
tmp: integer;
|
|
ndx: PIntegerArray;
|
|
begin
|
|
if n <= 1 then
|
|
begin
|
|
TempBuffer.buf := nil; // avoid GPF in TempBuffer.Done
|
|
result := 0;
|
|
exit;
|
|
end;
|
|
low := 0;
|
|
high := n - 1;
|
|
ndx := TempBuffer.InitIncreasing(n * 4); // no heap alloacation until n>1024
|
|
median := high shr 1;
|
|
repeat
|
|
if high <= low then
|
|
begin
|
|
// one item left
|
|
result := ndx[median];
|
|
{%H-}TempBuffer.Done;
|
|
exit;
|
|
end;
|
|
if high = low + 1 then
|
|
begin
|
|
// two items -> return the smallest (not average)
|
|
if OnCompare(ndx[low], ndx[high]) then
|
|
Exchg32(ndx[low], ndx[high]);
|
|
result := ndx[median];
|
|
{%H-}TempBuffer.Done;
|
|
exit;
|
|
end;
|
|
// find median of low, middle and high items; swap into position low
|
|
middle := (low + high) shr 1;
|
|
if OnCompare(ndx[middle], ndx[high]) then
|
|
Exchg32(ndx[middle], ndx[high]);
|
|
if OnCompare(ndx[low], ndx[high]) then
|
|
Exchg32(ndx[low], ndx[high]);
|
|
if OnCompare(ndx[middle], ndx[low]) then
|
|
Exchg32(ndx[middle], ndx[low]);
|
|
// swap low item (now in position middle) into position (low+1)
|
|
Exchg32(ndx[middle], ndx[low + 1]);
|
|
// nibble from each end towards middle, swapping items when stuck
|
|
ll := low + 1;
|
|
hh := high;
|
|
repeat
|
|
tmp := ndx[low];
|
|
repeat
|
|
inc(ll);
|
|
until not OnCompare(tmp, ndx[ll]);
|
|
repeat
|
|
dec(hh);
|
|
until not OnCompare(ndx[hh], tmp);
|
|
if hh < ll then
|
|
break;
|
|
tmp := ndx[ll];
|
|
ndx[ll] := ndx[hh];
|
|
ndx[hh] := tmp; // Exchg32(ndx[ll],ndx[hh]);
|
|
until false;
|
|
// swap middle item (in position low) back into correct position
|
|
Exchg32(ndx[low], ndx[hh]);
|
|
// next active partition
|
|
if hh <= median then
|
|
low := ll;
|
|
if hh >= median then
|
|
high := hh - 1;
|
|
until false;
|
|
end;
|
|
|
|
procedure NotifySortedIntegerChanges(old, new: PIntegerArray; oldn, newn: PtrInt;
|
|
const added, deleted: TOnNotifySortedIntegerChange; const sender);
|
|
var
|
|
o, n: PtrInt;
|
|
begin
|
|
o := 0;
|
|
n := 0;
|
|
repeat
|
|
while (n < newn) and
|
|
(o < oldn) and
|
|
(old[o] = new[n]) do
|
|
begin
|
|
inc(o);
|
|
inc(n);
|
|
end;
|
|
while (o < oldn) and
|
|
((n >= newn) or
|
|
(old[o] < new[n])) do
|
|
begin
|
|
if Assigned(deleted) then
|
|
deleted(sender, old[o]);
|
|
inc(o);
|
|
end;
|
|
while (n < newn) and
|
|
((o >= oldn) or
|
|
(new[n] < old[o])) do
|
|
begin
|
|
if Assigned(added) then
|
|
added(sender, new[n]);
|
|
inc(n);
|
|
end;
|
|
until (o >= oldn) and
|
|
(n >= newn);
|
|
end;
|
|
|
|
procedure CopyAndSortInteger(Values: PIntegerArray; ValuesCount: integer;
|
|
var Dest: TIntegerDynArray);
|
|
begin
|
|
if ValuesCount > Length(Dest) then
|
|
SetLength(Dest, ValuesCount);
|
|
MoveFast(Values^[0], Dest[0], ValuesCount * SizeOf(integer));
|
|
QuickSortInteger(pointer(Dest), 0, ValuesCount - 1);
|
|
end;
|
|
|
|
procedure CopyAndSortInt64(Values: PInt64Array; ValuesCount: integer;
|
|
var Dest: TInt64DynArray);
|
|
begin
|
|
if ValuesCount > Length(Dest) then
|
|
SetLength(Dest, ValuesCount);
|
|
MoveFast(Values^[0], Dest[0], ValuesCount * SizeOf(Int64));
|
|
QuickSortInt64(pointer(Dest), 0, ValuesCount - 1);
|
|
end;
|
|
|
|
procedure ExcludeInteger(var Values, Excluded: TIntegerDynArray; ExcludedSortSize: integer);
|
|
var
|
|
i, v, x, n: PtrInt;
|
|
begin
|
|
if (Values = nil) or
|
|
(Excluded = nil) then
|
|
exit; // nothing to exclude
|
|
EnsureUnique(Values);
|
|
EnsureUnique(Excluded);
|
|
v := Length(Values);
|
|
n := 0;
|
|
x := Length(Excluded);
|
|
if (x > ExcludedSortSize) or
|
|
(v > ExcludedSortSize) then
|
|
begin
|
|
// sort if worth it
|
|
dec(x);
|
|
QuickSortInteger(pointer(Excluded), 0, x);
|
|
for i := 0 to v - 1 do
|
|
if FastFindIntegerSorted(pointer(Excluded), x, Values[i]) < 0 then
|
|
begin
|
|
if n <> i then
|
|
Values[n] := Values[i];
|
|
inc(n);
|
|
end;
|
|
end
|
|
else
|
|
for i := 0 to v - 1 do
|
|
if not IntegerScanExists(pointer(Excluded), x, Values[i]) then
|
|
begin
|
|
if n <> i then
|
|
Values[n] := Values[i];
|
|
inc(n);
|
|
end;
|
|
if n <> v then
|
|
SetLength(Values, n);
|
|
end;
|
|
|
|
procedure IncludeInteger(var Values, Included: TIntegerDynArray; IncludedSortSize: integer);
|
|
var
|
|
i, v, x, n: PtrInt;
|
|
begin
|
|
if (Values = nil) or
|
|
(Included = nil) then
|
|
begin
|
|
Values := nil;
|
|
exit;
|
|
end;
|
|
EnsureUnique(Values);
|
|
EnsureUnique(Included);
|
|
v := Length(Values);
|
|
n := 0;
|
|
x := Length(Included);
|
|
if (x > IncludedSortSize) or
|
|
(v > IncludedSortSize) then
|
|
begin
|
|
// sort if worth it
|
|
dec(x);
|
|
QuickSortInteger(pointer(Included), 0, x);
|
|
for i := 0 to v - 1 do
|
|
if FastFindIntegerSorted(pointer(Included), x, Values[i]) >= 0 then
|
|
begin
|
|
if n <> i then
|
|
Values[n] := Values[i];
|
|
inc(n);
|
|
end;
|
|
end
|
|
else
|
|
for i := 0 to v - 1 do
|
|
if IntegerScanExists(pointer(Included), x, Values[i]) then
|
|
begin
|
|
if n <> i then
|
|
Values[n] := Values[i];
|
|
inc(n);
|
|
end;
|
|
if n <> v then
|
|
SetLength(Values, n);
|
|
end;
|
|
|
|
procedure ExcludeInt64(var Values, Excluded: TInt64DynArray; ExcludedSortSize: integer);
|
|
var
|
|
i, v, x, n: PtrInt;
|
|
begin
|
|
if (Values = nil) or
|
|
(Excluded = nil) then
|
|
exit; // nothing to exclude
|
|
v := Length(Values);
|
|
n := 0;
|
|
x := Length(Excluded);
|
|
if (x > ExcludedSortSize) or
|
|
(v > ExcludedSortSize) then
|
|
begin
|
|
// sort if worth it
|
|
dec(x);
|
|
QuickSortInt64(pointer(Excluded), 0, x);
|
|
for i := 0 to v - 1 do
|
|
if FastFindInt64Sorted(pointer(Excluded), x, Values[i]) < 0 then
|
|
begin
|
|
if n <> i then
|
|
Values[n] := Values[i];
|
|
inc(n);
|
|
end;
|
|
end
|
|
else
|
|
for i := 0 to v - 1 do
|
|
if not Int64ScanExists(pointer(Excluded), x, Values[i]) then
|
|
begin
|
|
if n <> i then
|
|
Values[n] := Values[i];
|
|
inc(n);
|
|
end;
|
|
if n <> v then
|
|
SetLength(Values, n);
|
|
end;
|
|
|
|
procedure IncludeInt64(var Values, Included: TInt64DynArray; IncludedSortSize: integer);
|
|
var
|
|
i, v, x, n: PtrInt;
|
|
begin
|
|
if (Values = nil) or
|
|
(Included = nil) then
|
|
begin
|
|
Values := nil;
|
|
exit;
|
|
end;
|
|
v := Length(Values);
|
|
n := 0;
|
|
x := Length(Included);
|
|
if (x > IncludedSortSize) or
|
|
(v > IncludedSortSize) then
|
|
begin
|
|
// sort if worth it
|
|
dec(x);
|
|
QuickSortInt64(pointer(Included), 0, x);
|
|
for i := 0 to v - 1 do
|
|
if FastFindInt64Sorted(pointer(Included), x, Values[i]) >= 0 then
|
|
begin
|
|
if n <> i then
|
|
Values[n] := Values[i];
|
|
inc(n);
|
|
end;
|
|
end
|
|
else
|
|
for i := 0 to v - 1 do
|
|
if Int64ScanExists(pointer(Included), x, Values[i]) then
|
|
begin
|
|
if n <> i then
|
|
Values[n] := Values[i];
|
|
inc(n);
|
|
end;
|
|
if n <> v then
|
|
SetLength(Values, n);
|
|
end;
|
|
|
|
procedure DeduplicateInteger(var Values: TIntegerDynArray);
|
|
begin
|
|
DeduplicateInteger(Values, Length(Values));
|
|
end;
|
|
|
|
function DeduplicateIntegerSorted(val: PIntegerArray; last: PtrInt): PtrInt;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
// sub-function for better code generation
|
|
i := 0;
|
|
repeat // here last>0 so i<last
|
|
if val[i] = val[i + 1] then
|
|
break;
|
|
inc(i);
|
|
if i <> last then
|
|
continue;
|
|
result := i;
|
|
exit;
|
|
until false;
|
|
result := i;
|
|
inc(i);
|
|
if i <> last then
|
|
begin
|
|
repeat
|
|
if val[i] <> val[i + 1] then
|
|
begin
|
|
val[result] := val[i];
|
|
inc(result);
|
|
end;
|
|
inc(i);
|
|
until i = last;
|
|
val[result] := val[i];
|
|
end;
|
|
end;
|
|
|
|
function DeduplicateInteger(var Values: TIntegerDynArray; Count: PtrInt): PtrInt;
|
|
begin
|
|
result := Count;
|
|
dec(Count);
|
|
if Count > 0 then
|
|
begin
|
|
QuickSortInteger(pointer(Values), 0, Count);
|
|
result := DeduplicateIntegerSorted(pointer(Values), Count) + 1;
|
|
end;
|
|
if result <> Length(Values) then
|
|
SetLength(Values, result);
|
|
end;
|
|
|
|
procedure DeduplicateInt64(var Values: TInt64DynArray);
|
|
begin
|
|
DeduplicateInt64(Values, Length(Values));
|
|
end;
|
|
|
|
function DeduplicateInt64Sorted(val: PInt64Array; last: PtrInt): PtrInt;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
// sub-function for better code generation
|
|
i := 0;
|
|
repeat // here last>0 so i<last
|
|
if val[i] = val[i + 1] then
|
|
break;
|
|
inc(i);
|
|
if i <> last then
|
|
continue;
|
|
result := i;
|
|
exit;
|
|
until false;
|
|
result := i;
|
|
inc(i);
|
|
if i <> last then
|
|
begin
|
|
repeat
|
|
if val[i] <> val[i + 1] then
|
|
begin
|
|
val[result] := val[i];
|
|
inc(result);
|
|
end;
|
|
inc(i);
|
|
until i = last;
|
|
val[result] := val[i];
|
|
end;
|
|
end;
|
|
|
|
function DeduplicateInt64(var Values: TInt64DynArray; Count: PtrInt): PtrInt;
|
|
begin
|
|
result := Count;
|
|
dec(Count);
|
|
if Count > 0 then
|
|
begin
|
|
QuickSortInt64(pointer(Values), 0, Count);
|
|
result := DeduplicateInt64Sorted(pointer(Values), Count) + 1;
|
|
end;
|
|
if result <> Length(Values) then
|
|
SetLength(Values, result);
|
|
end;
|
|
|
|
procedure CopyInteger(const Source: TIntegerDynArray; out Dest: TIntegerDynArray);
|
|
var
|
|
n: integer;
|
|
begin
|
|
n := Length(Source);
|
|
SetLength(Dest, n);
|
|
MoveFast(Source[0], Dest[0], n * SizeOf(integer));
|
|
end;
|
|
|
|
procedure CopyInt64(const Source: TInt64DynArray; out Dest: TInt64DynArray);
|
|
var
|
|
n: integer;
|
|
begin
|
|
n := Length(Source);
|
|
SetLength(Dest, n);
|
|
MoveFast(Source[0], Dest[0], n * SizeOf(Int64));
|
|
end;
|
|
|
|
function MaxInteger(const Values: TIntegerDynArray; ValuesCount: PtrInt; MaxStart: integer): integer;
|
|
var
|
|
i: PtrInt;
|
|
v: integer;
|
|
begin
|
|
result := MaxStart;
|
|
for i := 0 to ValuesCount - 1 do
|
|
begin
|
|
v := Values[i];
|
|
if v > result then
|
|
result := v; // movca branchless opcode on FPC
|
|
end;
|
|
end;
|
|
|
|
function SumInteger(const Values: TIntegerDynArray; ValuesCount: PtrInt): integer;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
result := 0;
|
|
for i := 0 to ValuesCount - 1 do
|
|
inc(result, Values[i]);
|
|
end;
|
|
|
|
procedure Reverse(const Values: TIntegerDynArray; ValuesCount: PtrInt; Reversed: PIntegerArray);
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
i := 0;
|
|
if ValuesCount >= 4 then
|
|
begin
|
|
dec(ValuesCount, 4);
|
|
while i < ValuesCount do
|
|
begin
|
|
// faster pipelined version
|
|
Reversed[Values[i]] := i;
|
|
Reversed[Values[i + 1]] := i + 1;
|
|
Reversed[Values[i + 2]] := i + 2;
|
|
Reversed[Values[i + 3]] := i + 3;
|
|
inc(i, 4);
|
|
end;
|
|
inc(ValuesCount, 4);
|
|
end;
|
|
while i < ValuesCount do
|
|
begin
|
|
Reversed[Values[i]] := i;
|
|
inc(i);
|
|
end;
|
|
//for i := 0 to Count-1 do Assert(Reverse[Orig[i]]=i);
|
|
end;
|
|
|
|
procedure Int64ToUInt32(Values64: PInt64Array; Values32: PCardinalArray; Count: PtrInt);
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
for i := 0 to Count - 1 do
|
|
Values32[i] := Values64[i];
|
|
end;
|
|
|
|
function AnyScanIndex(P, V: pointer; Count, VSize: PtrInt): PtrInt;
|
|
begin
|
|
case VSize of
|
|
// optimized versions for arrays of most simple types
|
|
1:
|
|
result := ByteScanIndex(P, Count, PByte(V)^); // SSE2 asm on Intel/AMD
|
|
2:
|
|
result := WordScanIndex(P, Count, PWord(V)^); // may use SSE2 asm
|
|
4:
|
|
result := IntegerScanIndex(P, Count, PInteger(V)^); // may use SSE2 asm
|
|
8:
|
|
result := Int64ScanIndex(P, Count, PInt64(V)^);
|
|
SizeOf(THash128):
|
|
result := Hash128Index(P, Count, V);
|
|
SizeOf(THash256):
|
|
result := Hash256Index(P, Count, V);
|
|
// small VSize version (<SizeOf(PtrInt))
|
|
3, 5..7:
|
|
begin
|
|
for result := 0 to Count - 1 do
|
|
if CompareMemSmall(P, V, VSize) then
|
|
exit
|
|
else
|
|
inc(PByte(P), VSize);
|
|
result := -1;
|
|
end;
|
|
else
|
|
begin
|
|
// generic binary comparison (fast with inlined CompareMemSmall)
|
|
for result := 0 to Count - 1 do
|
|
if (PInt64(P)^ = PInt64(V)^) and // not better using a local Int64 var
|
|
CompareMemSmall(PAnsiChar(P) + 8, PAnsiChar(V) + 8, VSize - 8) then
|
|
exit
|
|
else
|
|
inc(PByte(P), VSize);
|
|
result := -1;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function AnyScanExists(P, V: pointer; Count, VSize: PtrInt): boolean;
|
|
begin
|
|
result := AnyScanIndex(P, V, Count, VSize) >= 0;
|
|
end;
|
|
|
|
|
|
{ ************ Abstract Radix Tree Classes }
|
|
|
|
{ TRadixTreeNode }
|
|
|
|
function TRadixTreeNode.ComputeDepth: integer;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
result := 1;
|
|
for i := 0 to high(Child) do
|
|
inc(result, Child[i].ComputeDepth); // recursive calculation
|
|
Depth := result;
|
|
end;
|
|
|
|
function RadixTreeNodeCompare(const A, B): integer;
|
|
begin // sort static first, then deeper, by path:, by longest path, by text
|
|
result := ord(TRadixTreeNode(B).Chars[1] <> '<') -
|
|
ord(TRadixTreeNode(A).Chars[1] <> '<');
|
|
if result = 0 then
|
|
result := ord(IdemPChar(pointer(TRadixTreeNode(A).Chars), '<PATH:')) -
|
|
ord(IdemPChar(pointer(TRadixTreeNode(B).Chars), '<PATH:'));
|
|
if result = 0 then
|
|
result := CompareInteger(TRadixTreeNode(B).Depth, TRadixTreeNode(A).Depth);
|
|
if result = 0 then
|
|
result := CompareInteger(length(TRadixTreeNode(B).FullText),
|
|
length(TRadixTreeNode(A).FullText));
|
|
if result = 0 then
|
|
result := StrComp(pointer(TRadixTreeNode(A).FullText),
|
|
pointer(TRadixTreeNode(B).FullText));
|
|
end;
|
|
|
|
procedure TRadixTreeNode.SortChildren;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
for i := 0 to high(Child) do
|
|
Child[i].SortChildren; // recursive sorting
|
|
ObjArraySort(Child, RadixTreeNodeCompare);
|
|
end;
|
|
|
|
constructor TRadixTreeNode.Create(aOwner: TRadixTree);
|
|
begin
|
|
inherited Create;
|
|
Owner := aOwner;
|
|
end;
|
|
|
|
function TRadixTreeNode.Split(const Text: RawUtf8): TRadixTreeNode;
|
|
begin
|
|
result := TRadixTreeNodeClass(PPointer(self)^).Create(Owner);
|
|
result.Chars := Text;
|
|
result.FullText := FullText;
|
|
result.Child := Child;
|
|
result.Flags := Flags;
|
|
Chars := '';
|
|
FullText := '';
|
|
Child := nil;
|
|
Flags := [];
|
|
ObjArrayAdd(Child, result);
|
|
end;
|
|
|
|
destructor TRadixTreeNode.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
ObjArrayClear(Child);
|
|
end;
|
|
|
|
function TRadixTreeNode.Find(P: PUtf8Char): TRadixTreeNode;
|
|
var
|
|
c: PUtf8Char;
|
|
n: TDALen;
|
|
t: PNormTable;
|
|
ch: ^TRadixTreeNode;
|
|
begin
|
|
result := nil; // no match
|
|
t := Owner.fNormTable;
|
|
c := pointer(Chars);
|
|
repeat
|
|
if (t[P^] <> c^) or // may do LowerCaseSelf(Chars) at Insert()
|
|
(P^ = #0) then
|
|
break;
|
|
inc(P);
|
|
inc(c);
|
|
until false;
|
|
if c^ <> #0 then
|
|
exit; // not enough matched chars
|
|
// if we reached here, the text do match up to now
|
|
if P^ = #0 then
|
|
result := self // exact match found for this entry
|
|
else
|
|
begin
|
|
ch := pointer(Child);
|
|
if ch = nil then
|
|
exit;
|
|
n := PDALen(PAnsiChar(ch) - _DALEN)^ + _DAOFF;
|
|
repeat
|
|
if ch^.Chars[1] = t[P^] then
|
|
begin
|
|
result := ch^.Find(P);
|
|
if result <> nil then
|
|
exit; // match found in children
|
|
end;
|
|
inc(ch);
|
|
dec(n);
|
|
until n = 0;
|
|
end;
|
|
end;
|
|
|
|
procedure TRadixTreeNode.ToText(var Result: RawUtf8; Level: integer);
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
Append(Result, [RawUtf8OfChar(' ', Level), Chars, #10]);
|
|
for i := 0 to high(Child) do
|
|
Child[i].ToText(Result, Level + length(Chars));
|
|
end;
|
|
|
|
|
|
{ TRadixTree }
|
|
|
|
constructor TRadixTree.Create(aNodeClass: TRadixTreeNodeClass;
|
|
aOptions: TRadixTreeOptions);
|
|
begin
|
|
if aNodeClass = nil then
|
|
raise ERadixTree.CreateUtf8('%.Create with aNodeClass=nil', [self]);
|
|
fDefaultNodeClass := aNodeClass;
|
|
fOptions := aOptions;
|
|
if rtoCaseInsensitiveUri in aOptions then
|
|
fNormTable := @NormToLower
|
|
else
|
|
fNormTable := @NormToNorm;
|
|
fRoot := fDefaultNodeClass.Create(self); // with no text
|
|
end;
|
|
|
|
destructor TRadixTree.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
fRoot.Free; // will recursively free all nested children
|
|
end;
|
|
|
|
procedure TRadixTree.Clear;
|
|
begin
|
|
if self = nil then
|
|
exit;
|
|
fRoot.Free;
|
|
fRoot := fDefaultNodeClass.Create(self);
|
|
end;
|
|
|
|
function TRadixTree.Insert(Text: RawUtf8; Node: TRadixTreeNode;
|
|
NodeClass: TRadixTreeNodeClass): TRadixTreeNode;
|
|
var
|
|
match, textlen, nodelen, i: PtrInt;
|
|
chars: RawUtf8;
|
|
begin
|
|
result := nil;
|
|
if Text = '' then
|
|
exit;
|
|
if Node = nil then
|
|
Node := fRoot;
|
|
if rtoCaseInsensitiveUri in Options then
|
|
LowerCaseSelf(Text);
|
|
textlen := length(Text);
|
|
nodelen := length(Node.Chars);
|
|
// check how many chars of Text are within Node.Chars
|
|
match := 0;
|
|
while (match < textlen) and
|
|
(match < nodelen) and
|
|
(Text[match + 1] = Node.Chars[match + 1]) do
|
|
inc(match);
|
|
// insert the node where fits
|
|
chars := copy(Text, match + 1, maxInt);
|
|
if (match = 0) or
|
|
(Node = fRoot) or
|
|
((match < textlen) and
|
|
(match >= nodelen)) then
|
|
begin
|
|
// we can just insert a new leaf node
|
|
if chars <> '' then
|
|
for i := 0 to high(Node.Child) do
|
|
if Node.Child[i].Chars[1] = chars[1] then
|
|
begin
|
|
result := Insert(chars, Node.Child[i]); // recursive insertion
|
|
result.FullText := Text;
|
|
exit;
|
|
end;
|
|
end
|
|
else if match <> nodelen then
|
|
begin
|
|
// need to split the existing node
|
|
Node.Split(copy(Node.Chars, match + 1, maxInt)); // split children
|
|
Node.Chars := copy(Text, 1, match); // new shared root
|
|
if chars = '' then
|
|
begin
|
|
result := Node; // don't need a sub child - use shared root
|
|
result.FullText := Text;
|
|
exit;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
// match an existing node
|
|
result := Node;
|
|
exit;
|
|
end;
|
|
// create new leaf
|
|
if NodeClass = nil then
|
|
NodeClass := fDefaultNodeClass;
|
|
result := NodeClass.Create(self);
|
|
result.Chars := chars;
|
|
result.FullText := Text;
|
|
ObjArrayAdd(Node.Child, result);
|
|
end;
|
|
|
|
procedure TRadixTree.AfterInsert;
|
|
begin
|
|
fRoot.ComputeDepth;
|
|
fRoot.SortChildren;
|
|
end;
|
|
|
|
function TRadixTree.Find(const Text: RawUtf8): TRadixTreeNode;
|
|
var
|
|
n: TDALen;
|
|
c: AnsiChar;
|
|
ch: ^TRadixTreeNode;
|
|
begin
|
|
result := nil;
|
|
if (self = nil) or
|
|
(Text = '') then
|
|
exit;
|
|
ch := pointer(fRoot.Child);
|
|
if ch = nil then
|
|
exit;
|
|
n := PDALen(PAnsiChar(ch) - _DALEN)^ + _DAOFF;
|
|
c := fNormTable[Text[1]];
|
|
repeat
|
|
if ch^.Chars[1] = c then // recursive call if may match
|
|
begin
|
|
result := ch^.Find(pointer(Text));
|
|
if result <> nil then
|
|
exit;
|
|
end;
|
|
inc(ch);
|
|
dec(n);
|
|
until n = 0;
|
|
end;
|
|
|
|
function TRadixTree.ToText: RawUtf8;
|
|
begin
|
|
result := '';
|
|
if self <> nil then
|
|
fRoot.ToText(result, 0);
|
|
end;
|
|
|
|
|
|
{ TRadixTreeNodeParams }
|
|
|
|
function TRadixTreeNodeParams.Split(const Text: RawUtf8): TRadixTreeNode;
|
|
begin
|
|
result := inherited Split(Text);
|
|
TRadixTreeNodeParams(result).Names := Names;
|
|
Names := nil;
|
|
end;
|
|
|
|
function TRadixTreeNodeParams.Lookup(P: PUtf8Char; Ctxt: TObject): TRadixTreeNodeParams;
|
|
var
|
|
n: TDALen;
|
|
c: PUtf8Char;
|
|
t: PNormTable;
|
|
f: TRadixTreeNodeFlags;
|
|
ch: ^TRadixTreeNodeParams;
|
|
begin
|
|
result := nil; // no match
|
|
t := Owner.fNormTable;
|
|
if Names = nil then
|
|
begin
|
|
// static text
|
|
c := pointer(Chars);
|
|
if c <> nil then
|
|
begin
|
|
repeat
|
|
if (t^[P^] <> c^) or // may do LowerCaseSelf(Chars) at Insert()
|
|
(P^ = #0) then
|
|
break;
|
|
inc(P);
|
|
inc(c);
|
|
until false;
|
|
if c^ <> #0 then
|
|
exit; // not enough matched chars
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
// <named> parameter
|
|
c := P;
|
|
f := Flags;
|
|
if rtfParamInteger in f then // <int:name> or rtoIntegerParams
|
|
begin
|
|
if (P^ < '0') or (P^ > '9') then
|
|
exit; // void <integer> is not allowed
|
|
repeat
|
|
inc(P)
|
|
until (P^ < '0') or (P^ > '9');
|
|
if (P^ <> #0) and (P^ <> '?') and (P^ <> '/') then
|
|
exit; // not an integer
|
|
end
|
|
else if rtfParamPath in f then // <path:filename> or * as <path:path>
|
|
while (P^ <> #0) and (P^ <> '?') do
|
|
inc(P)
|
|
else // regular <param>
|
|
while (P^ <> #0) and (P^ <> '?') and (P^ <> '/') do
|
|
inc(P);
|
|
if (Ctxt <> nil) and not LookupParam(Ctxt, c, P - c) then
|
|
exit; // the parameter is not in the expected format for Ctxt
|
|
end;
|
|
// if we reached here, the URI do match up to now
|
|
if (P^ = #0) or (P^ = '?') then
|
|
begin
|
|
if (P^ = '?') and (Ctxt <> nil) then
|
|
LookupParam(Ctxt, P, -1); // store the inlined parameters position in Ctxt
|
|
result := self; // exact match found for this entry (excluding URI params)
|
|
exit;
|
|
end;
|
|
ch := pointer(Child);
|
|
if ch = nil then
|
|
exit;
|
|
n := PDALen(PAnsiChar(ch) - _DALEN)^ + _DAOFF;
|
|
repeat
|
|
if (ch^.Names <> nil) or
|
|
(ch^.Chars[1] = t^[P^]) then // recursive call only if worth it
|
|
begin
|
|
result := ch^.Lookup(P, Ctxt);
|
|
if result <> nil then
|
|
exit; // match found in children
|
|
end;
|
|
inc(ch);
|
|
dec(n);
|
|
until n = 0;
|
|
end;
|
|
|
|
|
|
{ TRadixTreeParams }
|
|
|
|
function TRadixTreeParams.Setup(const aFromUri: RawUtf8;
|
|
out aNames: TRawUtf8DynArray): TRadixTreeNodeParams;
|
|
var
|
|
u: PUtf8Char;
|
|
item, full: RawUtf8;
|
|
flags: TRadixTreeNodeFlags;
|
|
begin
|
|
u := pointer(TrimU(aFromUri));
|
|
if PosExChar('<', aFromUri) = 0 then
|
|
// a simple static route
|
|
result := Insert(aFromUri) as TRadixTreeNodeParams
|
|
else
|
|
// parse static..<param1>..static..<param2>..static into static/param nodes
|
|
repeat
|
|
GetNextItem(u, '<', item);
|
|
full := full + item;
|
|
result := Insert(full) as TRadixTreeNodeParams; // static (Names = nil)
|
|
if u = nil then
|
|
break;
|
|
GetNextItem(u, '>', item);
|
|
if item = '' then
|
|
raise ERadixTree.CreateUtf8('Void <> in %.Setup(''%'')', [self, aFromUri]);
|
|
flags := [rtfParam];
|
|
if IdemPChar(pointer(item), 'INT:') then
|
|
begin
|
|
delete(item, 1, 4);
|
|
include(flags, rtfParamInteger);
|
|
end
|
|
else if rtoIntegerParams in Options then
|
|
include(flags, rtfParamInteger);
|
|
if IdemPChar(pointer(item), 'PATH:') then
|
|
begin
|
|
delete(item, 1, 5);
|
|
include(flags, rtfParamPath);
|
|
end;
|
|
if FindRawUtf8(aNames{%H-}, item) >= 0 then
|
|
raise ERadixTree.CreateUtf8('Duplicated <%> in %.Setup(''%'')',
|
|
[item, self, aFromUri]);
|
|
AddRawUtf8(aNames, item);
|
|
full := full + '<' + item + '>'; // avoid name collision with static
|
|
result := Insert(full) as TRadixTreeNodeParams; // param (Names <> nil)
|
|
result.Names := copy(aNames); // each node has its own Names copy
|
|
result.Flags := flags;
|
|
if (u = nil) or
|
|
(u^ = #0) then
|
|
// TODO: detect wildchar incompatibilities with nested searches?
|
|
break;
|
|
if u^ <> '/' then
|
|
raise ERadixTree.CreateUtf8('Unexpected <%>% in %.Setup(''%'')',
|
|
[item, u^, self, aFromUri]);
|
|
until false;
|
|
AfterInsert; // compute Depth and sort by priority
|
|
end;
|
|
|
|
|
|
|
|
procedure InitializeUnit;
|
|
var
|
|
k: TRttiKind;
|
|
begin
|
|
// initialize RTTI low-level comparison functions
|
|
RTTI_ORD_COMPARE[roSByte] := @_BC_SByte;
|
|
RTTI_ORD_COMPARE[roUByte] := @_BC_UByte;
|
|
RTTI_ORD_COMPARE[roSWord] := @_BC_SWord;
|
|
RTTI_ORD_COMPARE[roUWord] := @_BC_UWord;
|
|
RTTI_ORD_COMPARE[roSLong] := @_BC_SLong;
|
|
RTTI_ORD_COMPARE[roULong] := @_BC_ULong;
|
|
{$ifdef FPC_NEWRTTI}
|
|
RTTI_ORD_COMPARE[roSQWord] := @_BC_SQWord;
|
|
RTTI_ORD_COMPARE[roUQWord] := @_BC_UQWord;
|
|
{$endif FPC_NEWRTTI}
|
|
RTTI_FLOAT_COMPARE[rfSingle] := @_BC_Single;
|
|
RTTI_FLOAT_COMPARE[rfDouble] := @_BC_Double;
|
|
RTTI_FLOAT_COMPARE[rfExtended] := @_BC_Extended;
|
|
RTTI_FLOAT_COMPARE[rfComp] := @_BC_SQWord; // PInt64 is the best
|
|
RTTI_FLOAT_COMPARE[rfCurr] := @_BC_SQWord;
|
|
// initialize RTTI binary persistence and high-level comparison functions
|
|
for k := succ(low(k)) to high(k) do
|
|
case k of
|
|
rkInteger,
|
|
rkEnumeration,
|
|
rkSet,
|
|
rkChar,
|
|
rkWChar
|
|
{$ifdef FPC}, rkBool{$endif}:
|
|
begin
|
|
RTTI_BINARYSAVE[k] := @_BS_Ord;
|
|
RTTI_BINARYLOAD[k] := @_BL_Ord;
|
|
RTTI_COMPARE[false, k] := @_BC_Ord;
|
|
RTTI_COMPARE[true, k] := @_BC_Ord;
|
|
end;
|
|
{$ifdef FPC} rkQWord, {$endif}
|
|
rkInt64:
|
|
begin
|
|
RTTI_BINARYSAVE[k] := @_BS_64;
|
|
RTTI_BINARYLOAD[k] := @_BL_64;
|
|
RTTI_COMPARE[false, k] := @_BC_64;
|
|
RTTI_COMPARE[true, k] := @_BC_64;
|
|
end;
|
|
rkFloat:
|
|
begin
|
|
RTTI_BINARYSAVE[k] := @_BS_Float;
|
|
RTTI_BINARYLOAD[k] := @_BS_Float;
|
|
RTTI_COMPARE[false, k] := @_BC_Float;
|
|
RTTI_COMPARE[true, k] := @_BC_Float;
|
|
end;
|
|
rkLString:
|
|
begin
|
|
RTTI_BINARYSAVE[k] := @_BS_String;
|
|
RTTI_BINARYLOAD[k] := @_BL_LString;
|
|
RTTI_COMPARE[false, k] := @_BC_LString;
|
|
RTTI_COMPARE[true, k] := @_BCI_LString;
|
|
end;
|
|
{$ifdef HASVARUSTRING}
|
|
rkUString:
|
|
begin
|
|
RTTI_BINARYSAVE[k] := @_BS_UString;
|
|
RTTI_BINARYLOAD[k] := @_BL_UString;
|
|
RTTI_COMPARE[false, k] := @_BC_WString;
|
|
RTTI_COMPARE[true, k] := @_BCI_WString;
|
|
end;
|
|
{$endif HASVARUSTRING}
|
|
rkWString:
|
|
begin
|
|
RTTI_BINARYSAVE[k] := @_BS_WString;
|
|
RTTI_BINARYLOAD[k] := @_BL_WString;
|
|
RTTI_COMPARE[false, k] := @_BC_WString;
|
|
RTTI_COMPARE[true, k] := @_BCI_WString;
|
|
end;
|
|
{$ifdef FPC}rkObject,{$else}{$ifdef UNICODE}rkMRecord,{$endif}{$endif}
|
|
rkRecord:
|
|
begin
|
|
RTTI_BINARYSAVE[k] := @_BS_Record;
|
|
RTTI_BINARYLOAD[k] := @_BL_Record;
|
|
RTTI_COMPARE[false, k] := @_BC_Record;
|
|
RTTI_COMPARE[true, k] := @_BCI_Record;
|
|
end;
|
|
rkDynArray:
|
|
begin
|
|
RTTI_BINARYSAVE[k] := @_BS_DynArray;
|
|
RTTI_BINARYLOAD[k] := @_BL_DynArray;
|
|
RTTI_COMPARE[false, k] := @_BC_DynArray;
|
|
RTTI_COMPARE[true, k] := @_BCI_DynArray;
|
|
end;
|
|
rkArray:
|
|
begin
|
|
RTTI_BINARYSAVE[k] := @_BS_Array;
|
|
RTTI_BINARYLOAD[k] := @_BL_Array;
|
|
RTTI_COMPARE[false, k] := @_BC_Array;
|
|
RTTI_COMPARE[true, k] := @_BCI_Array;
|
|
end;
|
|
rkVariant:
|
|
begin
|
|
RTTI_BINARYSAVE[k] := @_BS_Variant;
|
|
RTTI_BINARYLOAD[k] := @_BL_Variant;
|
|
RTTI_COMPARE[false, k] := @_BC_Variant;
|
|
RTTI_COMPARE[true, k] := @_BCI_Variant;
|
|
end;
|
|
rkClass:
|
|
begin
|
|
RTTI_COMPARE[false, k] := @_BC_Object;
|
|
RTTI_COMPARE[true, k] := @_BCI_Object;
|
|
end;
|
|
// unsupported types will contain nil
|
|
end;
|
|
// setup internal function wrappers
|
|
GetDataFromJson := _GetDataFromJson;
|
|
end;
|
|
|
|
|
|
initialization
|
|
InitializeUnit;
|
|
|
|
end.
|
|
|