/// 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(var DynArray: TArray; const Item): integer; overload; /// wrapper around TDynArray.Delete // - not very fast, but could be useful for simple code function DynArrayDelete(var DynArray: TArray; Index: PtrInt): boolean; overload; /// compare two dynamic arrays values function DynArrayCompare(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 AB 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 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 // - 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 parameter - * being synonymous to 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 are // always case-sensitive, because they are user-specific runtime variables) // - if values should be only plain integers, never alphabetical text - // you may also specify int:xxx for a single parameter, e.g. as 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 node TRadixTreeNodeParams = class(TRadixTreeNode) protected /// is called for each 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 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 patterns function Lookup(P: PUtf8Char; Ctxt: TObject): TRadixTreeNodeParams; end; /// implement an abstract Radix Tree with static or nodes TRadixTreeParams = class(TRadixTree) public /// low-level registration of a new URI path, with support // - returns the node matching the given URI // - called e.g. from TUriRouter.Rewrite/Run methods // - will recognize alphanumerical and 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(var DynArray: TArray; const Item): integer; begin result := DynArrayAdd(TypeInfo(TArray), DynArray, Item); end; function DynArrayDelete(var DynArray: TArray; Index: PtrInt): boolean; begin result := DynArrayDelete(TypeInfo(TArray), DynArray, Index); end; function DynArrayCompare(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 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 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 (= 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), ' 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 // parameter c := P; f := Flags; if rtfParamInteger in f then // or rtoIntegerParams begin if (P^ < '0') or (P^ > '9') then exit; // void 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 // or * as while (P^ <> #0) and (P^ <> '?') do inc(P) else // regular 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....static....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.