Repository URL to install this package:
|
Version:
3.2.0 ▾
|
{ **********************************************************************
This file is part of the Free Component Library (FCL)
Copyright (c) 2015 by the Free Pascal development team
Base for REST classes
See the file COPYING.FPC, included in this distribution,
for details about the copyright.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
**********************************************************************}
unit restbase;
{$mode objfpc}{$H+}
{ $DEFINE DEBUGBASEOBJMEMLEAK}
interface
uses
typinfo, fpjson, Classes, SysUtils, contnrs;
Type
ERESTAPI = Class(Exception);
TStringArray = Array of string;
TStringArrayArray = Array of TStringArray;
TUnicodeStringArray = Array of UnicodeString;
TIntegerArray = Array of Integer;
TInt64Array = Array of Int64;
TInt32Array = Array of Integer;
TFloatArray = Array of TJSONFloat;
TFloatArrayArray = Array of TFloatArray;
TDoubleArray = Array of TJSONFloat;
TDoubleArrayArray = Array of TDoubleArray;
TDateTimeArray = Array of TDateTime;
TBooleanArray = Array of boolean;
TChildType = (ctArray,ctObject);
TChildTypes = Set of TChildType;
{ TBaseObject }
TObjectOption = (ooStartRecordingChanges,ooCreateObjectOnGet);
TObjectOptions = set of TObjectOption;
TDateTimeType = (dtNone,dtDateTime,dtDate,dtTime);
Const
DefaultObjectOptions = [ooStartRecordingChanges]; // Default for constructor.
IndexShift = 3; // Number of bits reserved for flags.
Type
{$M+}
TBaseObject = CLass(TObject)
Private
FObjectOptions : TObjectOptions;
fadditionalProperties : TJSONObject;
FBits : TBits;
Function GetDynArrayProp(P: PPropInfo) : Pointer; virtual;
procedure SetArrayElements(AP: Pointer; ET: PTypeInfo; AValue: TJSONArray);
procedure SetDynArrayProp(P: PPropInfo; AValue : Pointer); virtual;
procedure SetObjectOptions(AValue: TObjectOptions);
Function GetAdditionalProperties : TJSONObject;
protected
{$ifdef ver2_6}
// Version 2.6.4 has a bug for i386 where the array cannot be set through RTTI.
// This is a helper method that sets the length of the array to the desired length,
// After which the new array pointer is read again.
// AName is guaranteed to be lowercase
Procedure SetArrayLength(const AName : String; ALength : Longint); virtual;
{$endif}
Procedure MarkPropertyChanged(AIndex : Integer);
Function IsDateTimeProp(Info : PTypeInfo) : Boolean;
Function DateTimePropType(Info : PTypeInfo) : TDateTimeType;
// Load properties
Procedure ClearProperty(P: PPropInfo); virtual;
Procedure SetBooleanProperty(P: PPropInfo; AValue: Boolean); virtual;
Procedure SetFloatProperty(P: PPropInfo; AValue: Extended); virtual;
Procedure SetInt64Property(P: PPropInfo; AValue: Int64); virtual;
{$ifndef ver2_6}
Procedure SetQWordProperty(P: PPropInfo; AValue: QWord); virtual;
{$endif}
Procedure SetIntegerProperty(P: PPropInfo; AValue: Integer); virtual;
Procedure SetStringProperty(P: PPropInfo; AValue: String); virtual;
Procedure SetArrayProperty(P: PPropInfo; AValue : TJSONArray); virtual;
Procedure SetObjectProperty(P: PPropInfo; AValue : TJSONObject); virtual;
Procedure SetSetProperty(P: PPropInfo; AValue : TJSONArray); virtual;
Procedure SetEnumProperty(P: PPropInfo; AValue : TJSONData); virtual;
// Save properties
Function GetBooleanProperty(P: PPropInfo) : TJSONData; virtual;
Function GetIntegerProperty(P: PPropInfo) : TJSONData; virtual;
Function GetInt64Property(P: PPropInfo) : TJSONData; virtual;
Function GetQwordProperty(P: PPropInfo) : TJSONData; virtual;
Function GetFloatProperty(P: PPropInfo) : TJSONData; virtual;
Function GetStringProperty(P: PPropInfo) : TJSONData; virtual;
Function GetSetProperty(P: PPropInfo) : TJSONData; virtual;
Function GetEnumeratedProperty(P: PPropInfo) : TJSONData; virtual;
Function GetArrayProperty(P: PPropInfo) : TJSONData; virtual;
Function GetObjectProperty(P: PPropInfo) : TJSONData; virtual;
// Clear properties on
Procedure ClearChildren(ChildTypes : TChildTypes); virtual;
Class Function ClearChildTypes : TChildTypes; virtual;
Public
Constructor Create(AOptions : TObjectOptions = DefaultObjectOptions); Virtual;
Destructor Destroy; override;
Procedure StartRecordPropertyChanges;
Procedure ClearPropertyChanges;
Procedure StopRecordPropertyChanges;
Function IsPropertyModified(Info : PPropInfo) : Boolean;
Function IsPropertyModified(const AName : String) : Boolean;
Class Function AllowAdditionalProperties : Boolean; virtual;
Class Function GetTotalPropCount : Integer; virtual;
Class Function GetCurrentPropCount : Integer; virtual;
Class Function GetParentPropCount : Integer; virtual;
Class Function ExportPropertyName(Const AName : String) : string; virtual;
Class Function CleanPropertyName(Const AName : String) : string;
Class Function CreateObject(Const AKind : String; AClass: TClass = Nil) : TBaseObject;
Class Procedure RegisterObject;
Class Function ObjectRestKind : String; virtual;
Procedure LoadPropertyFromJSON(Const AName : String; JSON : TJSONData); virtual;
Function SavePropertyToJSON(Info : PPropInfo) : TJSONData; virtual;
Procedure LoadFromJSON(JSON : TJSONObject); virtual;
Procedure SaveToJSON(JSON : TJSONObject); virtual;
Function SaveToJSON : TJSONObject;
Property ObjectOptions : TObjectOptions Read FObjectOptions Write SetObjectOptions;
Property additionalProperties : TJSONObject Read GetAdditionalProperties;
end;
TBaseObjectClass = Class of TBaseObject;
TObjectArray = Array of TBaseObject;
TObjectArrayArray = Array of TObjectArray;
TBaseListEnumerator = class
private
FList: TFPObjectList;
FPosition: Integer;
public
constructor Create(AList: TFPObjectList);
function GetCurrent: TBaseObject; virtual;
function MoveNext: Boolean;
property Current: TBaseObject read GetCurrent;
end;
TBaseListEnumeratorClass = Class of TBaseListEnumerator;
{ TBaseObjectList }
TBaseObjectList = Class(TBaseObject)
private
FList : TFPObjectList;
Protected
function GetO(Aindex : Integer): TBaseObject;
procedure SetO(Aindex : Integer; AValue: TBaseObject);
Class Function ObjectClass : TBaseObjectClass; virtual;
Function DoCreateEnumerator(AEnumClass : TBaseListEnumeratorClass) : TBaseListEnumerator;
Public
Constructor Create(AOptions : TObjectOptions = DefaultObjectOptions); Override;
Destructor Destroy; override;
function GetEnumerator : TBaseListEnumerator;
Function AddObject(Const AKind : String) : TBaseObject; virtual;
Property Objects [Aindex : Integer] : TBaseObject Read GetO Write SetO; default;
end;
{ TBaseObjectList }
{ TBaseNamedObjectList }
TBaseNamedObjectList = Class(TBaseObject)
private
FList : TStringList;
function GetN(Aindex : Integer): String;
function GetO(Aindex : Integer): TBaseObject;
function GetON(AName : String): TBaseObject;
procedure SetN(Aindex : Integer; AValue: String);
procedure SetO(Aindex : Integer; AValue: TBaseObject);
procedure SetON(AName : String; AValue: TBaseObject);
Protected
Class Function ObjectClass : TBaseObjectClass; virtual;
Public
Constructor Create(AOptions : TObjectOptions = DefaultObjectOptions); Override;
Destructor Destroy; override;
Function AddObject(Const AName,AKind : String) : TBaseObject; virtual;
Property Names [Aindex : Integer] : String Read GetN Write SetN;
Property Objects [Aindex : Integer] : TBaseObject Read GetO Write SetO;
Property ObjectByName [AName : String] : TBaseObject Read GetON Write SetON; default;
end;
// used to catch a general JSON schema.
{ TJSONSchema }
TJSONSchema = Class(TBaseObject)
private
FSchema: String;
Public
Procedure SetArrayProperty(P: PPropInfo; AValue : TJSONArray); override;
Procedure LoadFromJSON(JSON : TJSONObject); override;
Property Schema : String Read FSchema Write FSchema;
end;
TJSONSchemaArray = Array of TJSONSchema;
TTJSONSchemaArray = TJSONSchemaArray;
{ TObjectFactory }
TObjectFactory = Class(TComponent)
Private
FList : TClassList;
Public
Constructor Create(AOwner : TComponent); override;
Destructor Destroy; override;
Procedure RegisterObject(A : TBaseObjectClass);
Function GetObjectClass(Const AKind : String) : TBaseObjectClass;
end;
Function RESTFactory : TObjectFactory;
Function DateTimeToRFC3339(ADate :TDateTime):string;
Function DateToRFC3339(ADate :TDateTime):string;
Function TimeToRFC3339(ADate :TDateTime):string;
Function TryRFC3339ToDateTime(const Avalue: String; out ADateTime: TDateTime): Boolean;
Function RFC3339ToDateTime(const Avalue: String): TDateTime;
implementation
Var
Fact : TObjectFactory;
function DateTimeToRFC3339(ADate :TDateTime):string;
begin
Result:=FormatDateTime('yyyy-mm-dd"T"hh":"nn":"ss"."zzz"Z"',ADate);
end;
function DateToRFC3339(ADate: TDateTime): string;
begin
Result:=FormatDateTime('yyyy-mm-dd',ADate);
end;
function TimeToRFC3339(ADate :TDateTime):string;
begin
Result:=FormatDateTime('hh":"nn":"ss"."zzz',ADate);
end;
Function TryRFC3339ToDateTime(const Avalue: String; out ADateTime: TDateTime): Boolean;
// 1 2
// 12345678901234567890123
// yyyy-mm-ddThh:nn:ss.zzz
Type
TPartPos = (ppTime,ppYear,ppMonth,ppDay,ppHour,ppMinute,ppSec);
TPos = Array [TPartPos] of byte;
Const
P : TPos = (11,1,6,9,12,15,18);
var
lY, lM, lD, lH, lMi, lS: Integer;
begin
if Trim(AValue) = '' then
begin
Result:=True;
ADateTime:=0;
end;
lY:=StrToIntDef(Copy(AValue,P[ppYear],4),-1);
lM:=StrToIntDef(Copy(AValue,P[ppMonth],2),-1);
lD:=StrToIntDef(Copy(AValue,P[ppDay],2),-1);
if (Length(AValue)>=P[ppTime]) then
begin
lH:=StrToIntDef(Copy(AValue,P[ppHour],2),-1);
lMi:=StrToIntDef(Copy(AValue,P[ppMinute],2),-1);
lS:=StrToIntDef(Copy(AValue,P[ppSec],2),-1);
end
else
begin
lH:=0;
lMi:=0;
lS:=0;
end;
Result:=(lY>=0) and (lM>=00) and (lD>=0) and (lH>=0) and (lMi>=0) and (ls>=0);
if Not Result then
ADateTime:=0
else
{ Cannot EncodeDate if any part equals 0. EncodeTime is okay. }
if (lY = 0) or (lM = 0) or (lD = 0) then
ADateTime:=EncodeTime(lH, lMi, lS, 0)
else
ADateTime:=EncodeDate(lY, lM, lD) + EncodeTime(lH, lMi, lS, 0);
end;
Function CountProperties(TypeInfo : PTypeInfo; Recurse : Boolean): Integer;
function aligntoptr(p : pointer) : pointer;inline;
begin
{$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
result:=align(p,sizeof(p));
{$else FPC_REQUIRES_PROPER_ALIGNMENT}
result:=p;
{$endif FPC_REQUIRES_PROPER_ALIGNMENT}
end;
var
hp : PTypeData;
pd : ^TPropData;
begin
Result:=0;
while Assigned(TypeInfo) do
begin
// skip the name
hp:=GetTypeData(Typeinfo);
// the class info rtti the property rtti follows immediatly
pd:=aligntoptr(pointer(pointer(@hp^.UnitName)+Length(hp^.UnitName)+1));
Result:=Result+Pd^.PropCount;
if Recurse then
TypeInfo:=HP^.ParentInfo
else
TypeInfo:=Nil;
end;
end;
Function RFC3339ToDateTime(const Avalue: String): TDateTime;
begin
if Not TryRFC3339ToDateTime(AValue,Result) then
Result:=0;
end;
Function RESTFactory : TObjectFactory;
begin
if Fact=Nil then
Fact:=TObjectfactory.Create(Nil);
Result:=Fact;
end;
{ TObjectFactory }
Constructor TObjectFactory.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FList:=TClassList.Create;
end;
Destructor TObjectFactory.Destroy;
begin
FreeAndNil(FList);
inherited Destroy;
end;
Procedure TObjectFactory.RegisterObject(A: TBaseObjectClass);
begin
Flist.Add(A);
end;
Function TObjectFactory.GetObjectClass(Const AKind: String): TBaseObjectClass;
Var
I : Integer;
N : String;
begin
I:=FList.Count-1;
Result:=Nil;
While (Result=Nil) and (I>=0) do
begin
Result:=TBaseObjectClass(FList[i]);
N:=Result.ObjectRestKind;
if CompareText(N,AKind)<>0 then
Result:=nil;
Dec(I);
end;
end;
{ TBaseNamedObjectList }
function TBaseNamedObjectList.GetN(Aindex : Integer): String;
begin
Result:=Flist[AIndex];
end;
function TBaseNamedObjectList.GetO(Aindex: Integer): TBaseObject;
begin
Result:=TBaseObject(Flist.Objects[AIndex]);
end;
function TBaseNamedObjectList.GetON(AName : String): TBaseObject;
Var
I : Integer;
begin
I:=FList.IndexOf(AName);
if I<>-1 then
Result:=GetO(I)
else
Result:=Nil;
end;
procedure TBaseNamedObjectList.SetN(Aindex : Integer; AValue: String);
begin
Flist[AIndex]:=Avalue
end;
procedure TBaseNamedObjectList.SetO(Aindex: Integer; AValue: TBaseObject);
begin
Flist.Objects[AIndex]:=Avalue
end;
procedure TBaseNamedObjectList.SetON(AName : String; AValue: TBaseObject);
Var
I : Integer;
begin
I:=FList.IndexOf(AName);
if I<>-1 then
SetO(I,AValue)
else
Flist.AddObject(AName,AValue);
end;
Class Function TBaseNamedObjectList.ObjectClass: TBaseObjectClass;
begin
Result:=TBaseObject;
end;
Constructor TBaseNamedObjectList.Create(AOptions : TObjectOptions = DefaultObjectOptions);
begin
inherited Create(AOptions);
FList:=TStringList.Create;
Flist.OwnsObjects:=True;
end;
Destructor TBaseNamedObjectList.Destroy;
begin
FreeAndNil(Flist);
inherited Destroy;
end;
Function TBaseNamedObjectList.AddObject(Const AName, AKind: String
): TBaseObject;
begin
Result:=CreateObject(AKind);
ObjectByName[AName]:=Result;
end;
{ TJSONSchema }
Procedure TJSONSchema.SetArrayProperty(P: PPropInfo; AValue: TJSONArray);
begin
Schema:=AValue.asJSON
end;
Procedure TJSONSchema.LoadFromJSON(JSON: TJSONObject);
begin
Schema:=JSON.AsJSON;
end;
{ TBaseObjectList }
function TBaseObjectList.GetO(Aindex : Integer): TBaseObject;
begin
Result:=TBaseObject(FList[AIndex]);
end;
procedure TBaseObjectList.SetO(Aindex : Integer; AValue: TBaseObject);
begin
FList[AIndex]:=AValue;
end;
class function TBaseObjectList.ObjectClass: TBaseObjectClass;
begin
Result:=TBaseObject;
end;
function TBaseObjectList.DoCreateEnumerator(AEnumClass: TBaseListEnumeratorClass
): TBaseListEnumerator;
begin
Result:=AEnumClass.Create(FList);
end;
constructor TBaseObjectList.Create(AOptions: TObjectOptions);
begin
inherited Create(AOptions);
FList:=TFPObjectList.Create;
end;
destructor TBaseObjectList.Destroy;
begin
FreeAndNil(FList);
inherited Destroy;
end;
function TBaseObjectList.GetEnumerator: TBaseListEnumerator;
begin
Result:=TBaseListEnumerator.Create(FList);
end;
function TBaseObjectList.AddObject(const AKind: String): TBaseObject;
Var
C : TBaseObjectClass;
begin
if (AKind<>'') then
begin
C:=RestFactory.GetObjectClass(AKind);
if Not C.InheritsFrom(ObjectClass) then
Raise ERestAPI.CreateFmt('Cannot add object of kind "%s" to list, associated class "%s" is not a descendent of list class "%s"',[AKind,C.ClassName,ObjectClass.ClassName]);
end;
Result:=ObjectClass.Create;
FList.Add(Result);
end;
constructor TBAseListEnumerator.Create(AList: TFPObjectList);
begin
inherited Create;
FList := AList;
FPosition := -1;
end;
function TBaseListEnumerator.GetCurrent: TBaseObject;
begin
Result := TBaseObject(FList[FPosition]);
end;
function TBaseListEnumerator.MoveNext: Boolean;
begin
Inc(FPosition);
Result := FPosition < FList.Count;
end;
{ TBaseObject }
function TBaseObject.GetDynArrayProp(P: PPropInfo): Pointer;
begin
Result:=Pointer(GetObjectProp(Self,P));
end;
{ $DEFINE DUMPARRAY}
{$IFDEF DUMPARRAY}
Procedure DumpArray(ClassName,N : String; P : Pointer);
Type
pdynarray = ^tdynarray;
tdynarray = packed record
refcount : ptrint;
high : tdynarrayindex;
end;
Var
R : pdynarray;
begin
if P=Nil then
Writeln(ClassName,' property ',N, ' is nil')
else
begin
r:=pdynarray(p-sizeof(tdynarray));
Writeln(ClassName,' property ',N, ' has ref count ',r^.refcount,' and high ',r^.high);
end;
end;
{$ENDIF}
procedure TBaseObject.SetDynArrayProp(P: PPropInfo; AValue: Pointer);
begin
{$IFDEF DUMPARRAY}
DumpArray(ClassName+' (set)',P^.PropType^.Name,AValue);
{$ENDIF}
SetObjectProp(Self,P,TObject(AValue));
{$IFDEF DUMPARRAY}
DumpArray(ClassName+' (check)',P^.PropType^.Name,AValue);
{$ENDIF}
end;
procedure TBaseObject.SetObjectOptions(AValue: TObjectOptions);
begin
if FObjectOptions=AValue then Exit;
FObjectOptions:=AValue;
if ooStartRecordingChanges in FObjectOptions then
StartRecordPropertyChanges
end;
procedure TBaseObject.MarkPropertyChanged(AIndex: Integer);
begin
If Assigned(FBits) then
FBits.SetOn(GetParentPropCount+(AIndex shr IndexShift));
end;
function TBaseObject.IsDateTimeProp(Info: PTypeInfo): Boolean;
begin
Result:=DateTimePropType(Info)<>dtNone;
end;
function TBaseObject.DateTimePropType(Info: PTypeInfo): TDateTimeType;
begin
Result:=dtNone;
if (Info=TypeInfo(TDateTime)) then
Result:=dtDateTime
else if (Info=TypeInfo(TDate)) then
Result:=dtDate
else if (Info=TypeInfo(TTime)) then
Result:=dtTime
end;
procedure TBaseObject.ClearProperty(P: PPropInfo);
begin
Case P^.PropType^.Kind of
tkInteger,
tkChar,
tkEnumeration,
tkBool,
tkSet : SetOrdProp(Self,P,0);
tkFloat : SetFloatProp(Self,P,0.0);
tkSString,
tkLString,
tkUChar,
tkAString: SetStrProp(Self,P,'');
tkWChar,
tkWString: SetWideStrProp(Self,P,'');
tkUString: SetUnicodeStrProp(Self,P,'');
tkInt64,
tkQWord : SetInt64Prop(Self,P,0);
tkClass :
begin
GetObjectProp(Self,P).Free;
SetObjectProp(Self,P,Nil);
end
else
// Do nothing
end;
end;
procedure TBaseObject.SetBooleanProperty(P: PPropInfo; AValue: Boolean);
begin
SetOrdProp(Self,P,Ord(AValue));
end;
procedure TBaseObject.SetFloatProperty(P: PPropInfo; AValue: Extended);
begin
SetFloatProp(Self,P,AValue);
end;
procedure TBaseObject.SetIntegerProperty(P: PPropInfo; AValue: Integer);
begin
SetOrdProp(Self,P,AValue);
end;
procedure TBaseObject.SetInt64Property(P: PPropInfo; AValue: Int64);
begin
SetInt64Prop(Self,P,AValue);
end;
{$ifndef ver2_6}
procedure TBaseObject.SetQWordProperty(P: PPropInfo; AValue: QWord);
begin
SetInt64Prop(Self,P,Int64(AValue));
end;
{$endif}
procedure TBaseObject.SetStringProperty(P: PPropInfo; AValue: String);
Var
D : TDateTime;
begin
if not IsDateTimeProp(P^.PropType) then
SetStrProp(Self,P,AValue)
else if TryRFC3339ToDateTime(AValue,D) then
SetFloatProp(Self,P,D)
else
SetFloatProp(Self,P,0)
end;
procedure TBaseObject.SetArrayElements(AP : Pointer; ET: PTypeInfo; AValue: TJSONArray);
Var
I : Integer;
AN : String;
begin
AN:=ET^.Name;
// Fill in all elements
For I:=0 to AValue.Count-1 do
begin
Case ET^.Kind of
tkClass :
begin
TObjectArray(AP)[I]:=CreateObject(AN,GetTypeData(ET)^.ClassType);
TObjectArray(AP)[I].LoadFromJSON(AValue.Objects[i]);
end;
tkFloat :
if IsDateTimeProp(ET) then
TDateTimeArray(AP)[I]:=RFC3339ToDateTime(AValue.Strings[i])
else
begin
TFloatArray(AP)[I]:=AValue.Floats[i];
end;
tkInt64 :
TInt64Array(AP)[I]:=AValue.Int64s[i];
tkBool :
begin
TBooleanArray(AP)[I]:=AValue.Booleans[i];
end;
tkInteger :
TIntegerArray(AP)[I]:=AValue.Integers[i];
tkUstring,
tkWstring :
TUnicodeStringArray(AP)[I]:=UTF8Decode(AValue.Strings[i]);
tkString,
tkAstring,
tkLString :
begin
TStringArray(AP)[I]:=AValue.Strings[i];
end;
else
Raise ERESTAPI.CreateFmt('%s: unsupported array element type for property of type %s: %s',[ClassName,AN,GetEnumName(TypeInfo(TTypeKind),Ord(ET^.Kind))]);
end;
end;
end;
procedure TBaseObject.SetArrayProperty(P: PPropInfo; AValue: TJSONArray);
Var
T : PTypeData;
L : TBaseObjectList;
D : TJSONEnum;
O : TObjectArray;
I : Integer;
PTD : PTypeData;
ET : PTypeInfo;
LPN,AN : String;
AP : Pointer;
S : TJSONSchema;
begin
if P^.PropType^.Kind=tkClass then
begin
T:=GetTypeData(P^.PropType);
if T^.ClassType.InheritsFrom(TBaseObjectList) then
begin
L:=TBaseObjectList(TBaseObjectClass(T^.ClassType).Create);
SetObjectProp(Self,P,L);
For D in AValue do
L.AddObject('').LoadFromJSON(D.Value as TJSONObject);
end
else if T^.ClassType.InheritsFrom(TJSONSchema) then
begin
S:=TJSONSchema.Create;
S.SetArrayProperty(P,AValue);
SetObjectProp(Self,P,S);
end
else
Raise ERESTAPI.CreateFmt('Unsupported class %s for property %s',[T^.ClassType.ClassName,P^.Name]);
end
else if P^.PropType^.Kind=tkDynArray then
begin
// Get array value
AP:=GetObjectProp(Self,P);
PTD:=GetTypeData(P^.PropType);
ET:=PTD^.ElType2;
if (ET^.Kind=tkClass) then
begin
// get object type name
AN:=ET^.Name;
// Free all objects
O:=TObjectArray(AP);
For I:=0 to Length(O)-1 do
FreeAndNil(O[i]);
end;
// Clear array
{$ifdef ver2_6}
LPN:=Lowercase(P^.Name);
SetArrayLength(LPN,0);
{$else}
I:=0;
DynArraySetLength(AP,P^.PropType,1,@i);
{$endif}
// Now, set new length
I:=AValue.Count;
// Writeln(ClassName,' (Array) Setting length of array property ',P^.Name,' (type: ',P^.PropType^.Name,') to ',AValue.Count);
{$ifdef ver2_6}
// Workaround for bug in 2.6.4 that cannot set the array prop correctly.
// Call helper routine and re-get array value
SetArrayLength(LPN,i);
AP:=GetObjectProp(Self,P);
{$else}
DynArraySetLength(AP,P^.PropType,1,@i);
I:=Length(TObjectArray(AP));
// Writeln('Array length : ',I);
SetDynArrayProp(P,AP);
{$endif}
try
SetArrayElements(AP,ET,AValue);
finally
// Reduce ref. count, compiler does not do it for us for a pointer.
TObjectArray(AP):=Nil;
end;
end;
end;
procedure TBaseObject.SetObjectProperty(P: PPropInfo; AValue: TJSONObject);
Var
O : TBaseObject;
A: Pointer;
T : PTypeData;
D : TJSONEnum;
AN : String;
I : Integer;
L : TBaseObjectList;
NL : TBaseNamedObjectList;
begin
if P^.PropType^.Kind=tkDynArray then
begin
A:=GetDynArrayProp(P);
For I:=0 to Length(TObjectArray(A))-1 do
FreeAndNil(TObjectArray(A)[i]);
SetLength(TObjectArray(A),AValue.Count);
T:=GetTypeData(P^.PropType);
AN:=T^.ElType2^.Name;
I:=0;
For D in AValue do
begin
O:=CreateObject(AN);
TObjectArray(A)[I]:=O;
// Writeln(ClassName,' Adding instance of type: ',AN,' for key ',D.Key);
if IsPublishedProp(O,'name') then
SetStrProp(O,'name',D.Key);
O.LoadFromJSON(D.Value as TJSONObject);
Inc(I);
end;
// Writeln(ClassName,' Done with array ',P^.Name,', final array length: ', Length(TObjectArray(A)));
SetDynArrayProp(P,A);
Exit;
end;
if Not (P^.PropType^.Kind=tkClass) then
Raise ERESTAPI.CreateFmt('%s: Unsupported type for property %s',[ClassName,P^.Name]);
T:=GetTypeData(P^.PropType);
if T^.ClassType.InheritsFrom(TBaseObject) then
begin
O:=TBaseObject(GetObjectProp(Self,P,TBaseObject));
if O=Nil then
begin
O:=TBaseObjectClass(T^.ClassType).Create;
SetObjectProp(Self,P,O);
end;
O.LoadFromJSON(AValue);
end
else if T^.ClassType.InheritsFrom(TBaseObjectList) then
begin
L:=TBaseObjectList(TBaseObjectClass(T^.ClassType).Create);
SetObjectProp(Self,P,L);
For D in AValue do
L.AddObject('').LoadFromJSON(D.Value as TJSONObject);
end
else if T^.ClassType.InheritsFrom(TBaseNamedObjectList) then
begin
NL:=TBaseNamedObjectList(TBaseObjectClass(T^.ClassType).Create);
SetObjectProp(Self,P,L);
For D in AValue do
NL.AddObject(D.Key,'').LoadFromJSON(D.Value as TJSONObject);
end
else
Raise ERESTAPI.CreateFmt('%s: unsupported class %s for property %s',[ClassName, T^.ClassType.ClassName,P^.Name]);
end;
procedure TBaseObject.SetSetProperty(P: PPropInfo; AValue: TJSONArray);
type
TSet = set of 0..31;
var
S,I,V : Integer;
CurValue: string;
EnumTyp: PTypeInfo;
EnumTypData: PTypeData;
begin
S:=0;
EnumTyp:=GetTypeData(P^.PropType)^.CompType;
EnumTypData:=GetTypeData(EnumTyp);
For I:=0 to AValue.Count-1 do
begin
CurValue:=AValue.Strings[i];
if Not TryStrToInt(CurValue,V) then
V:=GetEnumValue(EnumTyp,CurValue);
if (V<EnumTypData^.MinValue) or (V>EnumTypData^.MaxValue) or (V>31) then
Raise ERESTAPI.CreateFmt('%s: Invalid value %s for property %s',[ClassName, CurValue,P^.Name]);
Include(TSet(S),V);
end;
SetOrdProp(Self,P,S);
end;
procedure TBaseObject.SetEnumProperty(P: PPropInfo; AValue: TJSONData);
Var
I : Integer;
begin
I:=GetEnumValue(P^.PropType,AValue.AsString);
if (I=-1) then
Raise ERESTAPI.CreateFmt('%s: Invalid value %s for property %s',[ClassName, AValue.AsString,P^.Name]);
SetOrdProp(Self,P,I);
end;
function TBaseObject.GetBooleanProperty(P: PPropInfo): TJSONData;
begin
Result:=TJSONBoolean.Create(GetOrdProp(Self,P)<>0);
end;
function TBaseObject.GetIntegerProperty(P: PPropInfo): TJSONData;
begin
Result:=TJSONIntegerNumber.Create(GetOrdProp(Self,P));
end;
function TBaseObject.GetInt64Property(P: PPropInfo): TJSONData;
begin
Result:=TJSONInt64Number.Create(GetInt64Prop(Self,P));
end;
function TBaseObject.GetQwordProperty(P: PPropInfo): TJSONData;
begin
Result:=TJSONInt64Number.Create(Int64(GetInt64Prop(Self,P)));
end;
function TBaseObject.GetFloatProperty(P: PPropInfo): TJSONData;
begin
Case DateTimePropType(P^.PropType) of
dtDateTime:
Result:=TJSONString.Create(DateTimeToRFC3339(GetFloatProp(Self,P)));
dtDate:
Result:=TJSONString.Create(DateToRFC3339(GetFloatProp(Self,P)));
dtTime:
Result:=TJSONString.Create(TimeToRFC3339(GetFloatProp(Self,P))) ;
else
Result:=TJSONFloatNumber.Create(GetFloatProp(Self,P));
end;
end;
function TBaseObject.GetStringProperty(P: PPropInfo): TJSONData;
begin
Result:=TJSONString.Create(GetStrProp(Self,P));
end;
function TBaseObject.GetSetProperty(P: PPropInfo): TJSONData;
type
TSet = set of 0..31;
var
Typ: PTypeInfo;
S, i: integer;
begin
Result:=TJSONArray.Create;
Typ:=GetTypeData(P^.PropType)^.CompType;
S:=GetOrdProp(Self,P);
for i:=Low(TSet) to High(TSet) do
if (i in TSet(S)) then
TJSONArray(Result).Add(TJSONString.Create(GetEnumName(Typ,i)));
end;
function TBaseObject.GetEnumeratedProperty(P: PPropInfo): TJSONData;
begin
Result:=TJSONString.Create(GetEnumProp(Self,P));
end;
function TBaseObject.GetArrayProperty(P: PPropInfo): TJSONData;
Var
AO : TObject;
I : Integer;
ET : PTypeInfo;
PTD : PTypeData;
AP : Pointer;
A : TJSONArray;
O : TJSONObject;
begin
A:=TJSONArray.Create;
Result:=A;
// Get array value type
AP:=GetObjectProp(Self,P);
PTD:=GetTypeData(P^.PropType);
ET:=PTD^.ElType2;
// Fill in all elements
Case ET^.Kind of
tkClass:
For I:=0 to Length(TObjectArray(AP))-1 do
begin
// Writeln(ClassName,' Adding instance of type: ',AN);
AO:=TObjectArray(AP)[I];
if AO.InheritsFrom(TBaseObject) then
begin
O:=TJSONObject.Create;
A.Add(O);
TBaseObject(AO).SaveToJSON(O);
end;
end;
tkFloat:
if IsDateTimeProp(ET) then
For I:=0 to Length(TDateTimeArray(AP))-1 do
A.Add(TJSONString.Create(DateTimeToRFC3339(TDateTimeArray(AP)[I])))
else
For I:=0 to Length(TFloatArray(AP))-1 do
A.Add(TJSONFloatNumber.Create(TFloatArray(AP)[I]));
tkInt64:
For I:=0 to Length(TInt64Array(AP))-1 do
A.Add(TJSONInt64Number.Create(TInt64Array(AP)[I]));
tkBool:
For I:=0 to Length(TInt64Array(AP))-1 do
A.Add(TJSONBoolean.Create(TBooleanArray(AP)[I]));
tkInteger :
For I:=0 to Length(TIntegerArray(AP))-1 do
A.Add(TJSONIntegerNumber.Create(TIntegerArray(AP)[I]));
tkUstring,
tkWstring :
For I:=0 to Length(TUnicodeStringArray(AP))-1 do
A.Add(TJSONString.Create(TUnicodeStringArray(AP)[I]));
tkString,
tkAstring,
tkLString :
For I:=0 to Length(TStringArray(AP))-1 do
A.Add(TJSONString.Create(TStringArray(AP)[I]));
else
Raise ERESTAPI.CreateFmt('%s: unsupported array element type : %s',[ClassName,GetEnumName(TypeInfo(TTypeKind),Ord(ET^.Kind))]);
end;
end;
function TBaseObject.GetObjectProperty(P: PPropInfo): TJSONData;
Var
O : TObject;
begin
O:=GetObjectProp(Self,P);
if (O is TBaseObject) then
Result:=TBaseObject(O).SaveToJSON
else
Result:=Nil; // maybe we need to add an option to return null ?
end;
procedure TBaseObject.ClearChildren(ChildTypes: TChildTypes);
Type
TObjectArr = Array of TObject;
var
PL: PPropList;
P : PPropInfo;
i,j,count,len:integer;
A : pointer;
PTD : PTypeData;
O : TObject;
begin
Count:=GetPropList(Self,PL);
try
for i:=0 to Count-1 do
begin
P:=PL^[I];
case P^.PropType^.Kind of
tkClass:
if (ctObject in ChildTypes) then
begin
// Writeln(ClassName,' Examining object: ',P^.Name);
O:=GetObjectProp(Self,P);
O.Free;
SetObjectProp(Self,P,Nil);
end;
tkDynArray:
if (ctArray in ChildTypes) then
begin
len:=Length(P^.PropType^.Name);
PTD:=GetTypeData(P^.PropType);
if PTD^.ElType2^.Kind=tkClass then
begin
A:=GetDynArrayProp(P);
{$IFDEF DUMPARRAY}
DumpArray(ClassName+' (clear)',P^.PropType^.Name,A);
{$ENDIF}
// Writeln(ClassName,' Examining array: ',P^.Name,'Count:',Length(TObjectArr(A)));
For J:=0 to Length(TObjectArr(A))-1 do
begin
FreeAndNil(TObjectArr(A)[J]);
end;
end;
// Length is set to nil by destructor
end;
end;
end;
finally
FreeMem(PL);
end;
end;
class function TBaseObject.ClearChildTypes: TChildTypes;
begin
Result:=[ctArray,ctObject]
end;
{$IFDEF DEBUGBASEOBJMEMLEAK}
Var
ObjCounter : TStrings;
{$ENDIF}
constructor TBaseObject.Create(AOptions: TObjectOptions);
begin
{$IFDEF DEBUGBASEOBJMEMLEAK}
if ObjCounter=Nil then
ObjCounter:=TStringList.Create;
ObjCounter.Values[ClassName]:=IntToStr(StrToIntDef(ObjCounter.Values[ClassName],0)+1);
{$ENDIF}
ObjectOptions:=AOptions;
// Do nothing
end;
destructor TBaseObject.Destroy;
begin
StopRecordPropertyChanges;
{$IFDEF DEBUGBASEOBJMEMLEAK}
ObjCounter.Values[ClassName]:=IntToStr(StrToIntDef(ObjCounter.Values[ClassName],0)-1);
{$ENDIF}
FreeAndNil(fadditionalProperties);
if ClearChildTypes<>[] then
ClearChildren(ClearChildTypes);
inherited;
end;
procedure TBaseObject.StartRecordPropertyChanges;
begin
if Assigned(FBits) then
FBits.ClearAll
else
FBits:=TBits.Create(GetTotalPropCount);
end;
procedure TBaseObject.ClearPropertyChanges;
begin
FBits.ClearAll;
end;
procedure TBaseObject.StopRecordPropertyChanges;
begin
FreeAndNil(FBits);
end;
function TBaseObject.IsPropertyModified(Info: PPropInfo): Boolean;
begin
Result:=Not Assigned(FBits) or FBits.Bits[Info^.NameIndex]
end;
function TBaseObject.IsPropertyModified(const AName: String): Boolean;
begin
Result:=IsPropertyModified(GetPropInfo(Self,AName));
end;
function TBaseObject.GetAdditionalProperties: TJSONObject;
begin
if (fAdditionalProperties=Nil) and AllowAdditionalProperties then
fAdditionalProperties:=TJSONObject.Create;
Result:=fAdditionalProperties
end;
{$IFDEF VER2_6}
procedure TBaseObject.SetArrayLength(Const AName: String; ALength: Longint);
begin
Raise ERestAPI.CreateFmt('Unknown Array %s',[AName]);
end;
{$ENDIF}
class function TBaseObject.AllowAdditionalProperties: Boolean;
begin
Result:=False;
end;
class function TBaseObject.ExportPropertyName(const AName: String): string;
begin
Result:=AName;
end;
class function TBaseObject.CleanPropertyName(const AName: String): string;
Const
KW=';absolute;and;array;asm;begin;case;const;constructor;destructor;div;do;'+
'downto;else;end;file;for;function;goto;if;implementation;in;inherited;'+
'inline;interface;label;mod;nil;not;object;of;on;operator;or;packed;'+
'procedure;program;record;reintroduce;repeat;self;set;shl;shr;string;then;'+
'to;type;unit;until;uses;var;while;with;xor;dispose;exit;false;new;true;'+
'as;class;dispinterface;except;exports;finalization;finally;initialization;'+
'inline;is;library;on;out;packed;property;raise;resourcestring;threadvar;try;'+
'private;published;length;setlength;';
Var
I : Integer;
begin
Result:=Aname;
For I:=Length(Result) downto 1 do
If Not ((Upcase(Result[i]) in ['_','A'..'Z'])
or ((I>1) and (Result[i] in (['0'..'9'])))) then
Delete(Result,i,1);
if Pos(';'+lowercase(Result)+';',KW)<>0 then
Result:='_'+Result
end;
class function TBaseObject.CreateObject(const AKind: String; AClass: TClass = Nil): TBaseObject;
Var
C : TBaseObjectClass;
begin
C:=RESTFactory.GetObjectClass(AKind);
if (C=Nil) and Assigned(AClass) and AClass.InheritsFrom(TBaseObject) then
C:=TBaseObjectClass(AClass);
if C<>Nil then
Result:=C.Create
else
Raise ERESTAPI.CreateFmt('Unknown class : "%s"',[AKind]);
// Do nothing
end;
class procedure TBaseObject.RegisterObject;
begin
RESTFactory.RegisterObject(Self);
end;
class function TBaseObject.ObjectRestKind: String;
begin
Result:=ClassName;
end;
class function TBaseObject.GetTotalPropCount: Integer;
begin
Result:=GetTypeData(ClassInfo)^.PropCount;
end;
class function TBaseObject.GetCurrentPropCount: Integer;
begin
Result:=CountProperties(ClassInfo,False);
end;
class function TBaseObject.GetParentPropCount: Integer;
begin
if (ClassParent=TBaseObject) or (ClassParent=Nil) then
Result:=0
else
Result:=TBaseObjectClass(ClassParent).GetTotalPropCount;
end;
procedure TBaseObject.LoadPropertyFromJSON(const AName: String; JSON: TJSONData
);
Var
P : PPropInfo;
o : TJSONObject;
begin
// Writeln(ClassName,' loading : ',ANAme,' -> ',CleanPropertyName(aName));
P:=GetPropInfo(Self,CleanPropertyName(aName));
if (P=Nil) then
begin
o:=additionalProperties;
if o=Nil then
Raise ERESTAPI.CreateFmt('%s : Unknown property "%s"',[ClassName,AName]);
o.Add(aName,JSON.Clone);
end
else
case JSON.JSONType of
jtstring :
if (P^.PropType^.Kind=tkEnumeration) then
SetEnumProperty(P,JSON)
else
SetStringproperty(P,JSON.AsString);
jtNumber :
case TJSONNumber(JSON).NumberType of
ntFloat : SetFloatProperty(P,JSON.asFloat);
ntInteger : SetIntegerProperty(P,JSON.asInteger);
ntInt64 : SetInt64Property(P,JSON.asInt64);
{$ifndef ver2_6}
ntqword : SetQWordProperty(P,JSON.asQWord);
{$endif}
end;
jtNull : ClearProperty(P);
jtBoolean : SetBooleanProperty(P,json.AsBoolean);
jtArray :
if P^.PropType^.Kind=tkSet then
SetSetProperty(P,TJSONArray(json))
else
SetArrayProperty(P,TJSONArray(json));
jtObject : SetObjectProperty(P,TJSONObject(json));
end;
end;
function TBaseObject.SavePropertyToJSON(Info: PPropInfo): TJSONData;
begin
Result:=Nil;
if Not IsPropertyModified(Info) then
Exit;
Case Info^.PropType^.Kind of
tkSet : Result:=GetSetProperty(Info);
tkEnumeration : Result:=GetEnumeratedProperty(Info);
tkAstring,
tkUstring,
tkWString,
tkwchar,
tkuchar,
tkString : Result:=GetStringProperty(Info);
tkFloat : Result:=GetFloatProperty(Info);
tkBool : Result:=GetBooleanProperty(Info);
tkClass : Result:=GetObjectProperty(Info);
tkDynArray : Result:=GetArrayProperty(Info);
tkQWord : Result:=GetQWordProperty(Info);
tkInt64 : Result:=GetInt64Property(Info);
tkInteger : Result:=GetIntegerProperty(Info);
end;
end;
procedure TBaseObject.LoadFromJSON(JSON: TJSONObject);
Var
D : TJSONEnum;
begin
StopRecordPropertyChanges;
For D in JSON Do
LoadPropertyFromJSON(D.Key,D.Value);
StartRecordPropertyChanges;
end;
procedure TBaseObject.SaveToJSON(JSON: TJSONObject);
var
PL: PPropList;
P : PPropInfo;
I,Count : integer;
D : TJSONData;
begin
Count:=GetPropList(Self,PL);
try
for i:=0 to Count-1 do
begin
P:=PL^[I];
D:=SavePropertyToJSON(P);
if (D<>Nil) then
JSON.add(ExportPropertyName(P^.Name),D);
end;
finally
FreeMem(PL);
end;
end;
function TBaseObject.SaveToJSON: TJSONObject;
begin
Result:=TJSONObject.Create;
try
SaveToJSON(Result);
except
FreeAndNil(Result);
Raise;
end;
end;
finalization
{$IFDEF DEBUGBASEOBJMEMLEAK}
if Assigned(ObjCounter) then
begin
Writeln(StdErr,'Object allocate-free count: ');
Writeln(StdErr,ObjCounter.Text);
FreeAndNil(ObjCounter);
end;
{$ENDIF}
FreeAndNil(Fact);
end.