Archive

Archive for June, 2013

Binary records for byterage

June 6, 2013 Leave a comment

I havent written that much about byterage before. I think it havent made even a dent in the delphi feeds around the world, but hopefully people will have a look at it and what it can do. To make a long story short it is a system that completely abstracts you from binary storage, allowing you to use the same methods to manipulate memory buffers, files, streams and whatever. Why is this important? Because advanced operations like insert, remove, push, poll and all the other goodies you would expect from a high-level language, simply havent been added to Delphi.

For instance, what if you want to insert 40k of data in the middle of a 10 gig file? With byterage this is a single call. What if you want to remove 40k (or whatever size) from a huge file? From the top, the middle or an offset of your choice? Once again this is a single call to a byterage method and it’s done. And it doesnt care if its a memory buffer, a stream or some other storage medium. You wont believe how easy it is to work with large files. This should have been added to TStream a long time ago.

Based on this system I have created what in Visual Studio is refered to as a “property-bag”. This is immensely useful no matter what you work with, be it databases, preferences for your app, shipping data over the internet (the list goes on). Think of it as a single record where you can add fields directly, write and read values of any datatype and save the whole shabam to file, memory, stream or whatever tickles your fancy.

You can read more about byterage here

And without further a-doo i present, the memRecord.pas (note: the byterage svn repo will be updated with all files shortly, this is just a taste):

  unit memRecord;

  interface

  uses sysutils, classes, math, contnrs,
  brage, jlElfHash;

  const
  ERR_RECORDFIELD_INVALIDNAME =
  'Invalid field name [%s] error';

  ERR_RECORDFIELD_FailedSet =
  'Writing to field buffer [%s] failed error';

  ERR_RECORDFIELD_FailedGet =
  'Reading from field buffer [%s] failed error';

  ERR_RECORDFIELD_FieldIsEmpty
  = 'Record field is empty [%s] error';

  CNT_RECORD_HEADER = $ABBABABE;

  type

  EJLRecordFieldError = Class(Exception);
  EJLRecordError      = Class(Exception);

  TJLRecordField = Class(TBRBufferMemory)
  Private
    FName:      String;
    FNameHash:  Int64;
    Procedure   SetName(Value:String);
  Protected
    Function    GetDisplayName:String;virtual;
    Procedure   BeforeReadObject;override;
    Procedure   ReadObject(Reader:TReader);override;
    Procedure   WriteObject(Writer:TWriter);override;
    Procedure   DoReleaseData;override;
  Protected
    Procedure   SignalWrite;
    Procedure   SignalRead;
    procedure   SignalRelease;
  Public
    function    asString:String;virtual;abstract;
    Property    DisplayName:String read GetDisplayName;
    Property    FieldSignature:Int64 read FNameHash;
    Property    FieldName:String read FName write SetName;
  End;

  TJLFieldBoolean = Class(TJLRecordField)
  Private
    Function    GetValue:Boolean;
    Procedure   SetValue(Const NewValue:Boolean);
  Protected
    Function    GetDisplayName:String;Override;
  Public
    Property    Value:Boolean read GetValue write SetValue;
    Function    asString:String;override;
  End;

  TJLFieldByte = Class(TJLRecordField)
  Private
    Function    GetValue:Byte;
    Procedure   SetValue(Const NewValue:Byte);
  Protected
    Function    GetDisplayName:String;Override;
  Public
    Property    Value:Byte read GetValue write SetValue;
    Function    asString:String;override;
  End;

  TJLFieldCurrency = Class(TJLRecordField)
  Private
    Function    GetValue:Currency;
    Procedure   SetValue(Const NewValue:Currency);
  Protected
    Function    GetDisplayName:String;Override;
  Public
    Property    Value:Currency read GetValue write SetValue;
    Function    asString:String;override;
  End;

  TJLFieldData = Class(TJLRecordField)
  Protected
    Function    GetDisplayName:String;Override;
  Public
    Function    asString:String;override;
  End;

  TJLFieldDateTime = Class(TJLRecordField)
  Private
    Function    GetValue:TDateTime;
    Procedure   SetValue(Const NewValue:TDateTime);
  Protected
    Function    GetDisplayName:String;Override;
  Public
    Property    Value:TDateTime read GetValue write SetValue;
    Function    asString:String;override;
  End;

  TJLFieldDouble = Class(TJLRecordField)
  Private
    Function    GetValue:Double;
    Procedure   SetValue(Const NewValue:Double);
  Protected
    Function    GetDisplayName:String;Override;
  Public
    Property    Value:Double read GetValue write SetValue;
    Function    asString:String;override;
  End;

  TJLFieldGUID = Class(TJLRecordField)
  Private
    Function    GetValue:TGUID;
    Procedure   SetValue(Const NewValue:TGUID);
  Protected
    Function    GetDisplayName:String;Override;
  Public
    Property    Value:TGUID read GetValue write SetValue;
    Function    asString:String;override;
  End;

  TJLFieldInteger = Class(TJLRecordField)
  Private
    Function    GetValue:Integer;
    Procedure   SetValue(Const NewValue:Integer);
  Protected
    Function    GetDisplayName:String;Override;
  Public
    Property    Value:Integer read GetValue write SetValue;
    Function    asString:String;override;
  End;

  TJLFieldInt64 = Class(TJLRecordField)
  Private
    Function    GetValue:Int64;
    Procedure   SetValue(Const NewValue:Int64);
  Protected
    Function    GetDisplayName:String;Override;
  Public
    Property    Value:Int64 read GetValue write SetValue;
    Function    asString:String;override;
  End;

  TJLFieldString = Class(TJLRecordField)
  Private
    FLength:    Integer;
    FExplicit:  Boolean;
    Function    GetValue:String;
    Procedure   SetValue(NewValue:String);
    Procedure   SetFieldLength(Value:Integer);
  Protected
    Function    GetDisplayName:String;Override;
  Public
    Property    Value:String read GetValue write SetValue;
    Property    Length:Integer read FLength write SetFieldLength;
    Property    Explicit:Boolean read FExplicit write FExplicit;
    Function    asString:String;override;
    Constructor Create;virtual;
  End;

  TJLFieldLong = Class(TJLRecordField)
  Private
    Function    GetValue:Longword;
    Procedure   SetValue(Const NewValue:Longword);
  Protected
    Function    GetDisplayName:String;Override;
  Public
    Property    Value:Longword read GetValue write SetValue;
    Function    asString:String;override;
  End;

  TJLRecordFieldClass = Class of TJLRecordField;
  TJLRecordFieldArray = Array of TJLRecordFieldClass;

  TJLCustomRecord = Class(TComponent)
  Private
    FObjects:   TObjectList;
    Function    GetCount:Integer;
    Function    GetItem(const Index:Integer):TJLRecordField;
    Procedure   SetItem(const Index:Integer;
                const Value:TJLRecordField);
    Function    GetField(const AName:String):TJLRecordField;
    Procedure   SetField(const AName:String;
                const Value:TJLRecordField);
  Protected
    Property    Fields[const aName:String]:TJLRecordField
                read GetField write SetField;
    Property    Items[const index:Integer]:TJLRecordField
                read GetItem write SetItem;
    Property    Count:Integer read GetCount;
  Public
    Function    Add(const aName:String;
                Const aFieldClass:TJLRecordFieldClass):TJLRecordField;
    Function    AddInteger(const aName:String):TJLFieldInteger;
    Function    AddStr(const aName:String):TJLFieldString;
    Function    AddByte(const aName:String):TJLFieldByte;
    Function    AddBool(const aName:String):TJLFieldBoolean;
    Function    AddCurrency(const aName:String):TJLFieldCurrency;
    Function    AddData(const aName:String):TJLFieldData;
    Function    AddDateTime(const aName:String):TJLFieldDateTime;
    Function    AddDouble(const aName:String):TJLFieldDouble;
    Function    AddGUID(const aName:String):TJLFieldGUID;
    Function    AddInt64(const aName:String):TJLFieldInt64;
    Function    AddLong(const aName:String):TJLFieldLong;

    Procedure   WriteInt(const aName:String;const Value:Integer);
    procedure   WriteStr(const aName:String;const Value:String);
    Procedure   WriteByte(const aName:String;const Value:Byte);
    procedure   WriteBool(const aName:String;const Value:Boolean);
    procedure   WriteCurrency(const aName:String;const Value:Currency);
    procedure   WriteData(const aName:String;const Value:TStream);
    procedure   WriteDateTime(const aName:String;const Value:TDateTime);
    procedure   WriteDouble(const aName:String;const Value:Double);
    Procedure   WriteGUID(const aName:String;const Value:TGUID);
    Procedure   WriteInt64(const aName:String;const Value:Int64);
    Procedure   WriteLong(const aName:String;const Value:Longword);

    Procedure   Clear;virtual;

    function    toStream:TStream;virtual;
    function    toBuffer:TBRBuffer;virtual;

    Procedure   SaveToStream(Const stream:TStream);virtual;
    procedure   LoadFromStream(Const stream:TStream);virtual;

    Function    IndexOf(const aName:String):Integer;
    Function    ObjectOf(const aName:String):TJLRecordField;
    Constructor Create(AOwner:TComponent);override;
    Destructor  Destroy;Override;
  End;

  TJLRecord = Class(TJLCustomRecord)
  Public
    Property  Fields;
    property  Items;
    Property  Count;
  End;

  Procedure JLRegisterRecordField(AClass:TJLRecordFieldClass);

  Function  JLRecordFieldKnown(AClass:TJLRecordFieldClass):Boolean;

  Function  JLRecordFieldClassFromName(aName:String;
            out AClass:TJLRecordFieldClass):Boolean;

  Function  JLRecordInstanceFromName(aName:String;
            out Value:TJLRecordField):Boolean;

  function JL_StrToGUID(const Value:AnsiString):TGUID;
  Function JL_GUIDToStr(const GUID:TGUID):AnsiString;

  implementation

  Var
  _FieldClasses:  TJLRecordFieldArray;

  const
  ERR_JL_InvalidGUID = '[%s] is not a valid GUID value';

  Function JL_GUIDToStr(const GUID:TGUID):AnsiString;
  begin
    SetLength(Result, 38);
    StrLFmt(@Result[1],38,'{%.8x-%.4x-%.4x-%.2x%.2x-%.2x%.2x%.2x%.2x%.2x%.2x}',
    [GUID.D1, GUID.D2, GUID.D3, GUID.D4[0], GUID.D4[1], GUID.D4[2], GUID.D4[3],
    GUID.D4[4], GUID.D4[5], GUID.D4[6], GUID.D4[7]]);
  end;

  function JL_StrToGUID(const Value:AnsiString):TGUID;
  var
    i:  Integer;
    src, dest: PAnsiChar;

    function _HexChar(Const C: AnsiChar): Byte;
    begin
      case C of
        '0'..'9': Result := Byte(c) - Byte('0');
        'a'..'f': Result := (Byte(c) - Byte('a')) + 10;
        'A'..'F': Result := (Byte(c) - Byte('A')) + 10;
      else        Raise Exception.CreateFmt(ERR_JL_InvalidGUID,[Value]);
      end;
    end;

    function _HexByte(Const P:PAnsiChar): AnsiChar;
    begin
      Result:=AnsiChar((_HexChar(p[0]) shl 4)+_HexChar(p[1]));
    end;

  begin
    If Length(Value)=38 then
    Begin
      dest := @Result;
      src := PAnsiChar(Value);
      Inc(src);

      for i := 0 to 3 do
      dest[i] := _HexByte(src+(3-i)*2);

      Inc(src, 8);
      Inc(dest, 4);
      if src[0] <> '-' then
      Raise Exception.CreateFmt(ERR_JL_InvalidGUID,[Value]);

      Inc(src);
      for i := 0 to 1 do
      begin
        dest^ := _HexByte(src+2);
        Inc(dest);
        dest^ := _HexByte(src);
        Inc(dest);
        Inc(src, 4);
        if src[0] <> '-' then
        Raise Exception.CreateFmt(ERR_JL_InvalidGUID,[Value]);
        inc(src);
      end;

      dest^ := _HexByte(src);
      Inc(dest);
      Inc(src, 2);
      dest^ := _HexByte(src);
      Inc(dest);
      Inc(src, 2);
      if src[0] <> '-' then
      Raise Exception.CreateFmt(ERR_JL_InvalidGUID,[Value]);

      Inc(src);
      for i := 0 to 5 do
      begin
        dest^:=_HexByte(src);
        Inc(dest);
        Inc(src, 2);
      end;
    end else
    Raise Exception.CreateFmt(ERR_JL_InvalidGUID,[Value]);
  end;

  Procedure JLRegisterRecordField(AClass:TJLRecordFieldClass);
  var
    FLen: Integer;
  Begin
    if (AClass<>NIL)
    and (JLRecordFieldKnown(AClass)=False) then
    Begin
      FLen:=Length(_FieldClasses);
      Setlength(_FieldClasses,FLen+1);
      _FieldClasses[FLen]:=AClass;
    end;
  end;

  Function JLRecordFieldKnown(AClass:TJLRecordFieldClass):Boolean;
  var
    x:  Integer;
  Begin
    result:=AClass<>NIl;
    if result then
    Begin
      result:=Length(_FieldClasses)>0;
      If result then
      begin
        result:=False;
        for x:=low(_FieldClasses) to high(_FieldClasses) do
        Begin
          result:=_FieldClasses[x]=AClass;
          if result then
          break;
        end;
      end;
    End;
  end;

  Function JLRecordFieldClassFromName(aName:String;
            out AClass:TJLRecordFieldClass):Boolean;
  var
    x:  Integer;
  Begin
    AClass:=NIL;
    result:=Length(_FieldClasses)>0;
    If result then
    begin
      result:=False;
      for x:=low(_FieldClasses) to high(_FieldClasses) do
      Begin
        result:=_FieldClasses[x].ClassName=aName;
        If result then
        Begin
          AClass:=_FieldClasses[x];
          break;
        end;
      end;
    end;
  end;

  Function  JLRecordInstanceFromName(aName:String;
            out Value:TJLRecordField):Boolean;
  var
    FClass: TJLRecordFieldClass;
  Begin
    result:=JLRecordFieldClassFromName(aName,FClass);
    if result then
    Value:=FClass.Create;
  end;

  //##########################################################################
  // TJLCustomRecord
  //##########################################################################

  Constructor TJLCustomRecord.Create(AOwner:TComponent);
  Begin
    inherited Create(AOwner);
    FObjects:=TObjectList.Create(True);
  end;

  Destructor TJLCustomRecord.Destroy;
  Begin
    FObjects.free;
    inherited;
  end;

  Procedure TJLCustomRecord.Clear;
  Begin
    FObjects.Clear;
  end;

  function TJLCustomRecord.toStream:TStream;
  Begin
    result:=TMemoryStream.Create;
    try
      SaveToStream(result);
      result.Position:=0;
    except
      on exception do
      Begin
        FreeAndNIL(result);
        Raise;
      end;
    end;
  end;

  function TJLCustomRecord.toBuffer:TBRBuffer;
  var
    mAdapter: TBRStreamAdapter;
  Begin
    result:=TBRBufferMemory.Create;
    try
      mAdapter:=TBRStreamAdapter.Create(result);
      try
        SaveToStream(mAdapter);
      finally
        mAdapter.Free;
      end;
    except
      on exception do
      Begin
        FreeAndNIL(result);
        Raise;
      end;
    end;
  end;

  Procedure TJLCustomRecord.SaveToStream(Const stream:TStream);
  var
    x:  Integer;
    mWriter:  TWriter;
    mHead:  Longword;
  Begin
    mWriter:=TWriter.Create(stream,1024);
    try
      mHead:=CNT_RECORD_HEADER;
      mWriter.Write(mHead,SizeOf(mHead));
      mWriter.WriteInteger(FObjects.Count);
      for x:=0 to FObjects.Count-1 do
      Begin
        mWriter.WriteString(items[x].ClassName);
        items[x].WriteObject(mWriter);
      end;
    finally
      mWriter.FlushBuffer;
      mWriter.Free;
    end;
  end;

  procedure TJLCustomRecord.LoadFromStream(Const stream:TStream);
  var
    x:  Integer;
    mReader:  TReader;
    mHead:  Longword;
    mCount: Integer;
    mName:  String;
    mField: TJLRecordField;
  Begin
    Clear;
    mReader:=TReader.Create(stream,1024);
    try
      mReader.Read(mHead,SizeOf(mHead));
      if mHead=CNT_RECORD_HEADER then
      begin
        mCount:=mReader.ReadInteger;
        for x:=0 to mCount-1 do
        Begin
          mName:=mReader.ReadString;
          if memRecord.JLRecordInstanceFromName(mName,mField) then
          Begin
            self.FObjects.Add(mField);
            mField.ReadObject(mReader);
          end else
          Raise EJLRecordError.CreateFmt
          ('Unknown field class [%s] error',[mName]);
        end;
      end else
      Raise EJLRecordError.Create('Invalid record header error');
    finally
      mReader.Free;
    end;
  end;

  Procedure TJLCustomRecord.WriteInt(const aName:String;const Value:Integer);
  var
    mRef: TJLRecordField;
  Begin
    mRef:=ObjectOf(aName);
    if mRef=NIL then
    mRef:=Add(aName,TJLFieldInteger);
    TJLFieldInteger(mRef).Value:=Value;
  end;

  procedure TJLCustomRecord.WriteStr(const aName:String;const Value:String);
  var
    mRef: TJLRecordField;
  Begin
    mRef:=ObjectOf(aName);
    if mRef=NIL then
    mRef:=Add(aName,TJLFieldString);
    TJLFieldString(mRef).Value:=Value;
  end;

  Procedure TJLCustomRecord.WriteByte(const aName:String;const Value:Byte);
  var
    mRef: TJLRecordField;
  Begin
    mRef:=ObjectOf(aName);
    if mRef=NIL then
    mRef:=Add(aName,TJLFieldByte);
    TJLFieldByte(mRef).Value:=Value;
  end;

  procedure TJLCustomRecord.WriteBool(const aName:String;const Value:Boolean);
  var
    mRef: TJLRecordField;
  Begin
    mRef:=ObjectOf(aName);
    if mRef=NIL then
    mRef:=Add(aName,TJLFieldBoolean);
    TJLFieldBoolean(mRef).Value:=Value;
  end;

  procedure TJLCustomRecord.WriteCurrency(const aName:String;
            const Value:Currency);
  var
    mRef: TJLRecordField;
  Begin
    mRef:=ObjectOf(aName);
    if mRef=NIL then
    mRef:=Add(aName,TJLFieldCurrency);
    TJLFieldCurrency(mRef).Value:=Value;
  end;

  procedure TJLCustomRecord.WriteData(const aName:String;const Value:TStream);
  var
    mRef: TJLRecordField;
  Begin
    mRef:=ObjectOf(aName);
    if mRef=NIL then
    mRef:=Add(aName,TJLFieldData);
    if value<>NIL then
    TJLFieldData(mRef).LoadFromStream(value);
  end;

  procedure TJLCustomRecord.WriteDateTime(const aName:String;
            const Value:TDateTime);
  var
    mRef: TJLRecordField;
  Begin
    mRef:=ObjectOf(aName);
    if mRef=NIL then
    mRef:=Add(aName,TJLFieldDateTime);
    TJLFieldDateTime(mRef).Value:=Value;
  end;

  procedure TJLCustomRecord.WriteDouble(const aName:String;
            const Value:Double);
  var
    mRef: TJLRecordField;
  Begin
    mRef:=ObjectOf(aName);
    if mRef=NIL then
    mRef:=Add(aName,TJLFieldDouble);
    TJLFieldDouble(mRef).Value:=Value;
  end;

  Procedure TJLCustomRecord.WriteGUID(const aName:String;
            const Value:TGUID);
  var
    mRef: TJLRecordField;
  Begin
    mRef:=ObjectOf(aName);
    if mRef=NIL then
    mRef:=Add(aName,TJLFieldGUID);
    TJLFieldGUID(mRef).Value:=Value;
  end;

  Procedure TJLCustomRecord.WriteInt64(const aName:String;
            const Value:Int64);
  var
    mRef: TJLRecordField;
  Begin
    mRef:=ObjectOf(aName);
    if mRef=NIL then
    mRef:=Add(aName,TJLFieldInt64);
    TJLFieldInt64(mRef).Value:=Value;
  end;

  Procedure TJLCustomRecord.WriteLong(const aName:String;
            const Value:Longword);
  var
    mRef: TJLRecordField;
  Begin
    mRef:=ObjectOf(aName);
    if mRef=NIL then
    mRef:=Add(aName,TJLFieldLong);
    TJLFieldLong(mRef).Value:=Value;
  end;

  Function TJLCustomRecord.AddInteger(const aName:String):TJLFieldInteger;
  Begin
    result:=TJLFieldInteger(Add(aName,TJLFieldInteger));
  end;

  Function TJLCustomRecord.AddStr(const aName:String):TJLFieldString;
  Begin
    result:=TJLFieldString(Add(aName,TJLFieldString));
  end;

  Function TJLCustomRecord.AddByte(const aName:String):TJLFieldByte;
  Begin
    result:=TJLFieldByte(Add(aName,TJLFieldByte));
  end;

  Function TJLCustomRecord.AddBool(const aName:String):TJLFieldBoolean;
  Begin
    result:=TJLFieldBoolean(Add(aName,TJLFieldBoolean));
  end;

  Function TJLCustomRecord.AddCurrency(const aName:String):TJLFieldCurrency;
  Begin
    result:=TJLFieldCurrency(Add(aName,TJLFieldCurrency));
  end;

  Function TJLCustomRecord.AddData(const aName:String):TJLFieldData;
  Begin
    result:=TJLFieldData(Add(aName,TJLFieldData));
  end;

  Function TJLCustomRecord.AddDateTime(const aName:String):TJLFieldDateTime;
  Begin
    result:=TJLFieldDateTime(Add(aName,TJLFieldDateTime));
  end;

  Function TJLCustomRecord.AddDouble(const aName:String):TJLFieldDouble;
  Begin
    result:=TJLFieldDouble(Add(aName,TJLFieldDouble));
  end;

  Function TJLCustomRecord.AddGUID(const aName:String):TJLFieldGUID;
  Begin
    result:=TJLFieldGUID(Add(aName,TJLFieldGUID));
  end;

  Function TJLCustomRecord.AddInt64(const aName:String):TJLFieldInt64;
  Begin
    result:=TJLFieldInt64(Add(aName,TJLFieldInt64));
  end;

  Function TJLCustomRecord.AddLong(const aName:String):TJLFieldLong;
  Begin
    result:=TJLFieldLong(Add(aName,TJLFieldLong));
  end;

  Function TJLCustomRecord.Add(const aName:String;
           Const aFieldClass:TJLRecordFieldClass):TJLRecordField;
  Begin
    result:=ObjectOf(aName);
    if result=NIL then
    Begin
      if aFieldClass<>NIL then
      Begin
        Result:=aFieldClass.Create;
        Result.FieldName:=aName;
        FObjects.Add(result);
      end else
      result:=NIL;
    end;
  end;

  Function TJLCustomRecord.GetCount:Integer;
  Begin
    result:=FObjects.Count;
  end;

  Function TJLCustomRecord.GetItem(const Index:Integer):TJLRecordField;
  Begin
    result:=TJLRecordField(FObjects[index]);
  end;

  Procedure TJLCustomRecord.SetItem(const Index:Integer;
            const value:TJLRecordField);
  Begin
    TJLRecordField(FObjects[index]).Assign(Value);
  end;

  Function TJLCustomRecord.GetField(const AName:String):TJLRecordField;
  Begin
    result:=ObjectOf(aName);
  end;

  Procedure TJLCustomRecord.SetField(const AName:String;
            const Value:TJLRecordField);
  var
    FItem: TJLRecordField;
  Begin
    FItem:=ObjectOf(aName);
    If FItem<>NIL then
    FItem.assign(Value);
  end;

  Function TJLCustomRecord.IndexOf(const aName:String):Integer;
  var
    x:  integer;
  Begin
    result:=-1;
    if length(aName)>0 then
    Begin
      for x:=0 to FObjects.Count-1 do
      Begin
        if sameText(GetItem(x).FieldName,aName) then
        Begin
          result:=x;
          Break;
        end;
      end;
    end;
  end;

  Function TJLCustomRecord.ObjectOf(const aName:String):TJLRecordField;
  var
    x:      integer;
    FItem:  TJLRecordField;
  Begin
    result:=NIL;
    if length(aName)>0 then
    Begin
      for x:=0 to FObjects.Count-1 do
      Begin
        FItem:=GetItem(x);
        if sameText(FItem.FieldName,aName) then
        Begin
          result:=FItem;
          Break;
        end;
      end;
    end;
  end;

  //##########################################################################
  // TJLFieldLong
  //##########################################################################

  Function TJLFieldLong.asString:String;
  Begin
    Result:=IntToStr(Value);
  end;

  Function TJLFieldLong.GetDisplayName:String;
  Begin
    Result:='Longword';
  end;

  Function TJLFieldLong.GetValue:Longword;
  Begin
    If not Empty then
    Begin
      If Read(0,SizeOf(Result),Result)<SizeOf(Result) then
      Raise EJLRecordFieldError.CreateFmt
      (ERR_RECORDFIELD_FailedGet,[FieldName]) else
      SignalRead;
    end else
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FieldIsEmpty,[FieldName]);
  end;

  Procedure TJLFieldLong.SetValue(Const NewValue:Longword);
  Begin
    If Write(0,SizeOf(NewValue),NewValue)<SizeOf(NewValue) then
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FailedSet,[FieldName]) else
    SignalWrite;
  end;

  //##########################################################################
  // TSRLFieldInt64
  //##########################################################################

  Function TJLFieldInt64.asString:String;
  Begin
    Result:=IntToStr(Value);
  end;

  Function TJLFieldInt64.GetDisplayName:String;
  Begin
    Result:='Int64';
  end;

  Function TJLFieldInt64.GetValue:Int64;
  Begin
    If not Empty then
    Begin
      If Read(0,SizeOf(Result),Result)<SizeOf(Result) then
      Raise EJLRecordFieldError.CreateFmt
      (ERR_RECORDFIELD_FailedGet,[FieldName]) else
      SignalRead;
    end else
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FieldIsEmpty,[FieldName]);
  end;

  Procedure TJLFieldInt64.SetValue(Const NewValue:Int64);
  Begin
    If Write(0,SizeOf(NewValue),NewValue)<SizeOf(NewValue) then
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FailedSet,[FieldName]) else
    SignalWrite;
  end;

  //##########################################################################
  // TJLFieldInteger
  //##########################################################################

  Function TJLFieldInteger.asString:String;
  Begin
    Result:=IntToStr(Value);
  end;

  Function TJLFieldInteger.GetDisplayName:String;
  Begin
    Result:='Integer';
  end;

  Function TJLFieldInteger.GetValue:Integer;
  Begin
    If not Empty then
    Begin
      If Read(0,SizeOf(Result),Result)<SizeOf(Result) then
      Raise EJLRecordFieldError.CreateFmt
      (ERR_RECORDFIELD_FailedGet,[FieldName]) else
      SignalRead;
    end else
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FieldIsEmpty,[FieldName]);
  end;

  Procedure TJLFieldInteger.SetValue(Const NewValue:Integer);
  Begin
    If Write(0,SizeOf(NewValue),NewValue)<SizeOf(NewValue) then
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FailedSet,[FieldName]) else
    SignalWrite;
  end;

  //##########################################################################
  // TJLFieldGUID
  //##########################################################################

  Function TJLFieldGUID.asString:String;
  Begin
    Result:=String(JL_GUIDToStr(Value));
  end;

  Function TJLFieldGUID.GetDisplayName:String;
  Begin
    Result:='GUID';
  end;

  Function TJLFieldGUID.GetValue:TGUID;
  Begin
    If not Empty then
    Begin
      If Read(0,SizeOf(Result),Result)<SizeOf(Result) then
      Raise EJLRecordFieldError.CreateFmt
      (ERR_RECORDFIELD_FailedGet,[FieldName]) else
      SignalRead;
    end else
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FieldIsEmpty,[FieldName]);
  end;

  Procedure TJLFieldGUID.SetValue(Const NewValue:TGUID);
  Begin
    If Write(0,SizeOf(NewValue),NewValue)<SizeOf(NewValue) then
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FailedSet,[FieldName]) else
    SignalWrite;
  end;

  //##########################################################################
  // TJLFieldDateTime
  //##########################################################################

  Function TJLFieldDateTime.asString:String;
  Begin
    Result:=DateTimeToStr(Value);
  end;

  Function TJLFieldDateTime.GetDisplayName:String;
  Begin
    Result:='DateTime';
  end;

  Function TJLFieldDateTime.GetValue:TDateTime;
  Begin
    If not Empty then
    Begin
      If Read(0,SizeOf(Result),Result)<SizeOf(Result) then
      Raise EJLRecordFieldError.CreateFmt
      (ERR_RECORDFIELD_FailedGet,[FieldName]) else
      SignalRead;
    end else
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FieldIsEmpty,[FieldName]);
  end;

  Procedure TJLFieldDateTime.SetValue(Const NewValue:TDateTime);
  Begin
    If Write(0,SizeOf(NewValue),NewValue)<SizeOf(NewValue) then
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FailedSet,[FieldName]) else
    SignalWrite;
  end;

  //##########################################################################
  // TJLFieldDouble
  //##########################################################################

  Function TJLFieldDouble.asString:String;
  Begin
    Result:=FloatToStr(Value);
  end;

  Function TJLFieldDouble.GetDisplayName:String;
  Begin
    Result:='Double';
  end;

  Function TJLFieldDouble.GetValue:Double;
  Begin
    If not Empty then
    Begin
      If Read(0,SizeOf(Result),Result)<SizeOf(Result) then
      Raise EJLRecordFieldError.CreateFmt
      (ERR_RecordField_FailedGet,[FieldName]) else
      SignalRead;
    end else
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FieldIsEmpty,[FieldName]);
  end;

  Procedure TJLFieldDouble.SetValue(Const NewValue:Double);
  Begin
    If Write(0,SizeOf(NewValue),NewValue)<SizeOf(NewValue) then
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FailedSet,[FieldName]) else
    SignalWrite;
  end;

  //##########################################################################
  // TJLFieldData
  //##########################################################################

  Function TJLFieldData.asString:String;
  Begin
    Result:='[Binary]';
  end;

  Function TJLFieldData.GetDisplayName:String;
  Begin
    Result:='Binary';
  end;

  //##########################################################################
  // TJLFieldCurrency
  //##########################################################################

  Function TJLFieldCurrency.asString:String;
  Begin
    Result:=CurrToStr(Value);
  end;

  Function TJLFieldCurrency.GetDisplayName:String;
  Begin
    Result:='Currency';
  end;

  Function TJLFieldCurrency.GetValue:Currency;
  Begin
    If not Empty then
    Begin
      If Read(0,SizeOf(Result),Result)<SizeOf(Result) then
      Raise EJLRecordFieldError.CreateFmt
      (ERR_RECORDFIELD_FailedGet,[FieldName]) else
      SignalRead;
    end else
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FieldIsEmpty,[FieldName]);
  end;

  Procedure TJLFieldCurrency.SetValue(Const NewValue:Currency);
  Begin
    If Write(0,SizeOf(NewValue),NewValue)<SizeOf(NewValue) then
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FailedSet,[FieldName]) else
    SignalWrite;
  end;

  //##########################################################################
  // TJLFieldByte
  //##########################################################################

  Function TJLFieldByte.asString:String;
  Begin
    Result:=IntToStr(Value);
  end;

  Function TJLFieldByte.GetDisplayName:String;
  Begin
    Result:='Byte';
  end;

  Function TJLFieldByte.GetValue:Byte;
  Begin
    If not Empty then
    Begin
      If Read(0,SizeOf(Result),Result)<SizeOf(Result) then
      Raise EJLRecordFieldError.CreateFmt
      (ERR_RECORDFIELD_FailedGet,[FieldName]) else
      SignalRead;
    end else
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FieldIsEmpty,[FieldName]);
  end;

  Procedure TJLFieldByte.SetValue(Const NewValue:Byte);
  Begin
    If Write(0,SizeOf(NewValue),NewValue)<SizeOf(NewValue) then
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FailedSet,[FieldName]) else
    SignalWrite;
  end;

  //##########################################################################
  // TJLFieldBoolean
  //##########################################################################

  Function TJLFieldBoolean.asString:String;
  Begin
    Result:=BoolToStr(Value,True);
  end;

  Function TJLFieldBoolean.GetDisplayName:String;
  Begin
    Result:='Boolean';
  end;

  Function TJLFieldBoolean.GetValue:Boolean;
  Begin
    If not Empty then
    Begin
      If Read(0,SizeOf(Result),Result)<SizeOf(Result) then
      Raise EJLRecordFieldError.CreateFmt
      (ERR_RECORDFIELD_FailedGet,[FieldName]) else
      SignalRead;
    end else
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FieldIsEmpty,[FieldName]);
  end;

  Procedure TJLFieldBoolean.SetValue(Const NewValue:Boolean);
  Begin
    If Write(0,SizeOf(NewValue),NewValue)<SizeOf(NewValue) then
    Raise EJLRecordFieldError.CreateFmt
    (ERR_RECORDFIELD_FailedSet,[FieldName]) else
    SignalWrite;
  end;

  //##########################################################################
  // TJLFieldString
  //##########################################################################

  Constructor TJLFieldString.Create;
  Begin
    inherited;
    FLength:=0;
    FExplicit:=False;
  end;

  Function TJLFieldString.asString:String;
  Begin
    Result:=Value;
  end;

  Function TJLFieldString.GetDisplayName:String;
  Begin
    Result:='String';
  end;

  Procedure TJLFieldString.SetFieldLength(Value:Integer);
  Begin
    If  FExplicit
    and (Value<>FLength) then
    Begin
      Value:=math.EnsureRange(Value,0,MAXINT-1);
      If Value>0 then
      Begin
        FLength:=Value;
        If FLength<>Size then
        Size:=FLength;
      end else
      Begin
        FLength:=0;
        Release;
      end;
    end;
  end;

  Function TJLFieldString.GetValue:String;
  Begin
    If not Empty then
    Begin
      SetLength(Result,Size);
      If Read(0,Size,pointer(@Result[1])^)<Size then
      Raise EJLRecordFieldError.CreateFmt
      (ERR_RECORDFIELD_FailedGet,[FieldName]) else
      SignalRead;
    end else
    Result:='';
  end;

  Procedure TJLFieldString.SetValue(NewValue:String);
  var
    FLen: Integer;
  Begin
    FLen:=system.Length(NewValue);
    If FLen>0 then
    Begin
      (* cut string to length if explicit *)
      If FExplicit then
      Begin
        If FLen>FLength then
        FLen:=FLength;
      end else
      Size:=FLen * SizeOf(char);

      If FLen>0 then
      Begin
        If Write(0,Size,NewValue[1])<Size then
        Raise EJLRecordFieldError.CreateFmt
        (ERR_RECORDFIELD_FailedSet,[FieldName]) else
        SignalWrite;
      end else
      Release;

    end else
    Release;
  end;

  //##########################################################################
  // TJLRecordField
  //##########################################################################

  Function TJLRecordField.GetDisplayName:String;
  Begin
    Result:='Unknown';
  end;

  Procedure TJLRecordField.SignalWrite;
  Begin
  end;

  Procedure TJLRecordField.SignalRead;
  Begin
  end;

  Procedure TJLRecordField.SignalRelease;
  Begin
  end;

  Procedure TJLRecordField.SetName(Value:String);
  Begin
    If Value<>FName then
    Begin
      Value:=trim(Value);
      If system.Length(Value)>0 then
      Begin
        FName:=Value;
        FNameHash:=TElfHash.fromString(LowerCase(Value));
      end else
      Raise EJLRecordFieldError.CreateFmt
      (ERR_RECORDFIELD_INVALIDNAME,[Value]);
    end;
  end;

  Procedure TJLRecordField.BeforeReadObject;
  Begin
    inherited;
    FName:='';
    FNameHash:=0;
  end;

  Procedure TJLRecordField.ReadObject(Reader:TReader);
  Begin
    inherited;
    FNameHash:=Reader.ReadInt64;
    FName:=Reader.ReadString;
  end;

  Procedure TJLRecordField.WriteObject(Writer:TWriter);
  Begin
    inherited;
    Writer.WriteInteger(FNameHash);
    Writer.WriteString(FName);
  end;

  Procedure TJLRecordField.DoReleaseData;
  Begin
    inherited;
    SignalRelease;
  end;

  Initialization
  Begin
    JLRegisterRecordField(TJLFieldBoolean);
    JLRegisterRecordField(TJLFieldByte);
    JLRegisterRecordField(TJLFieldCurrency);
    JLRegisterRecordField(TJLFieldData);
    JLRegisterRecordField(TJLFieldDateTime);
    JLRegisterRecordField(TJLFieldDouble);
    JLRegisterRecordField(TJLFieldGUID);
    JLRegisterRecordField(TJLFieldInt64);
    JLRegisterRecordField(TJLFieldInteger);
    JLRegisterRecordField(TJLFieldLong);
    JLRegisterRecordField(TJLFieldString);
  end;

  Finalization
  SetLength(_FieldClasses,0);

  end.