delphimvcframework/sources/MVCFramework.DuckTyping.pas

451 lines
13 KiB
ObjectPascal
Raw Normal View History

// ***************************************************************************
//
// Delphi MVC Framework
//
// Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team
//
// https://github.com/danieleteti/delphimvcframework
//
// ***************************************************************************
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ***************************************************************************
2013-10-30 00:48:23 +01:00
unit MVCFramework.DuckTyping;
2013-10-30 00:48:23 +01:00
{$LEGACYIFEND ON}
2013-10-30 00:48:23 +01:00
interface
uses
System.Rtti,
System.Classes,
System.Generics.Collections,
System.SysUtils,
System.TypInfo,
System.Math;
2013-10-30 00:48:23 +01:00
type
EMVCDuckTypingException = class(Exception);
TDuckTypedList = class;
2013-10-30 00:48:23 +01:00
IList = interface
2013-10-30 00:48:23 +01:00
['{2A1BCB3C-17A2-4F8D-B6FB-32B2A1BFE840}']
function Add(const AValue: TObject): Integer;
2013-10-30 00:48:23 +01:00
procedure Clear;
function Count: Integer;
function GetItem(AIndex: Integer): TObject;
2013-10-30 00:48:23 +01:00
end;
TDuckListEnumerator = class(TEnumerator<TObject>)
private
2014-03-07 23:16:33 +01:00
FPosition: Int64;
2013-10-30 00:48:23 +01:00
FDuckTypedList: TDuckTypedList;
protected
function DoGetCurrent: TObject; override;
function DoMoveNext: Boolean; override;
2013-10-30 00:48:23 +01:00
public
constructor Create(const ADuckTypedList: TDuckTypedList);
2013-10-30 00:48:23 +01:00
end;
TSortingType = (soAscending, soDescending);
2013-10-30 00:48:23 +01:00
IMVCList = interface
['{35BFF7E7-7CDA-4DCF-8618-33B9E92EA7CA}']
function GetItem(const AIndex: Integer): TObject;
function GetEnumerator: TDuckListEnumerator;
function GetOwnsObjects: Boolean;
procedure SetOwnsObjects(const AValue: Boolean);
2013-10-30 00:48:23 +01:00
function Count: Integer;
procedure Add(const AObject: TObject);
procedure Clear;
function WrappedObject: TObject;
procedure Sort(const APropertyName: string; AOrder: TSortingType = soAscending);
property OwnsObjects: Boolean read GetOwnsObjects write SetOwnsObjects;
2013-10-30 00:48:23 +01:00
end;
IWrappedList = IMVCList;
TDuckTypedList = class(TInterfacedObject, IMVCList)
private
2017-02-09 19:33:59 +01:00
class var
GlContext: TRttiContext;
GlObjectAsDuck: TObject;
GlAddMethod: TRttiMethod;
GlClearMethod: TRttiMethod;
GlCountProperty: TRttiProperty;
GlGetItemMethod: TRttiMethod;
GlGetCountMethod: TRttiMethod;
private
class constructor Create;
class destructor Destroy;
private
FOwnsObject: Boolean;
class procedure ClearRttiData;
protected
function GetOwnsObjects: Boolean;
procedure SetOwnsObjects(const AValue: Boolean);
function GetEnumerator: TDuckListEnumerator;
2013-10-30 00:48:23 +01:00
function Count: Integer;
function GetItem(const AIndex: Integer): TObject;
2013-10-30 00:48:23 +01:00
procedure Add(const AObject: TObject);
procedure Clear;
procedure QuickSort(AList: IMVCList; L, R: Integer; ACompare: TFunc<TObject, TObject, Integer>); overload;
procedure QuickSort(AList: IMVCList; ACompare: TFunc<TObject, TObject, Integer>); overload;
procedure Sort(const APropertyName: string; AOrder: TSortingType = soAscending);
2013-10-30 00:48:23 +01:00
function WrappedObject: TObject;
public
constructor Create(const AObjectAsDuck: TObject; AOwnsObject: Boolean = False);
2013-10-30 00:48:23 +01:00
destructor Destroy; override;
class function CanBeWrappedAsList(const AObjectAsDuck: TObject): Boolean; overload;
class function CanBeWrappedAsList(const AInterfaceAsDuck: IInterface): Boolean; overload;
class function Wrap(const AObjectAsDuck: TObject): IMVCList;
2013-10-30 00:48:23 +01:00
end;
function WrapAsList(const AObject: TObject; AOwnsObject: Boolean = False): IMVCList;
2013-10-30 00:48:23 +01:00
implementation
uses
MVCFramework.RttiUtils,
MVCFramework.Commons;
{ TDuckListEnumerator }
2013-10-30 00:48:23 +01:00
constructor TDuckListEnumerator.Create(const ADuckTypedList: TDuckTypedList);
2013-10-30 00:48:23 +01:00
begin
inherited Create;
FDuckTypedList := ADuckTypedList;
2014-03-07 23:16:33 +01:00
FPosition := -1;
2013-10-30 00:48:23 +01:00
end;
function TDuckListEnumerator.DoGetCurrent: TObject;
2013-10-30 00:48:23 +01:00
begin
if (FPosition > -1) then
2013-10-30 00:48:23 +01:00
Result := FDuckTypedList.GetItem(FPosition)
else
raise EMVCDuckTypingException.Create('Enumerator error: Call MoveNext first');
2013-10-30 00:48:23 +01:00
end;
function TDuckListEnumerator.DoMoveNext: Boolean;
2013-10-30 00:48:23 +01:00
begin
if (FPosition < FDuckTypedList.Count - 1) then
2013-10-30 00:48:23 +01:00
begin
Inc(FPosition);
Result := True;
end
else
Result := False;
2013-10-30 00:48:23 +01:00
end;
{ TDuckTypedList }
function TDuckTypedList.GetEnumerator: TDuckListEnumerator;
2013-10-30 00:48:23 +01:00
begin
Result := TDuckListEnumerator.Create(Self);
2013-10-30 00:48:23 +01:00
end;
procedure TDuckTypedList.Add(const AObject: TObject);
begin
if Assigned(GlAddMethod) then
GlAddMethod.Invoke(GlObjectAsDuck, [AObject]);
2013-10-30 00:48:23 +01:00
end;
class function TDuckTypedList.CanBeWrappedAsList(const AObjectAsDuck: TObject): Boolean;
2013-10-30 00:48:23 +01:00
var
ListObjType: TRttiType;
2013-10-30 00:48:23 +01:00
begin
ListObjType := GlContext.GetType(AObjectAsDuck.ClassInfo);
Result := (ListObjType.GetMethod('Add') <> nil) and (ListObjType.GetMethod('Clear') <> nil)
{$IF CompilerVersion >= 23}
2013-10-30 00:48:23 +01:00
and (ListObjType.GetIndexedProperty('Items') <> nil) and (ListObjType.GetIndexedProperty('Items').ReadMethod <> nil)
2013-10-30 00:48:23 +01:00
{$IFEND}
and (ListObjType.GetMethod('GetItem') <> nil) or (ListObjType.GetMethod('GetElement') <> nil) and (ListObjType.GetProperty('Count') <> nil);
end;
class function TDuckTypedList.CanBeWrappedAsList(const AInterfaceAsDuck: IInterface): Boolean;
var
ListObjType: TRttiType;
begin
ListObjType := GlContext.GetType(TObject(AInterfaceAsDuck).ClassInfo);
Result := (ListObjType.GetMethod('Add') <> nil) and (ListObjType.GetMethod('Clear') <> nil)
{$IF CompilerVersion >= 23}
2013-10-30 00:48:23 +01:00
and (ListObjType.GetIndexedProperty('Items') <> nil) and (ListObjType.GetIndexedProperty('Items').ReadMethod <> nil)
{$IFEND}
and (ListObjType.GetMethod('GetItem') <> nil) or (ListObjType.GetMethod('GetElement') <> nil) and (ListObjType.GetProperty('Count') <> nil)
2013-10-30 00:48:23 +01:00
end;
procedure TDuckTypedList.Clear;
begin
if Assigned(GlClearMethod) then
GlClearMethod.Invoke(GlObjectAsDuck, []);
end;
class procedure TDuckTypedList.ClearRttiData;
begin
GlObjectAsDuck := nil;
GlAddMethod := nil;
GlClearMethod := nil;
GlCountProperty := nil;
GlGetItemMethod := nil;
GlGetCountMethod := nil;
2013-10-30 00:48:23 +01:00
end;
function TDuckTypedList.Count: Integer;
begin
Result := 0;
if Assigned(GlCountProperty) then
Result := GlCountProperty.GetValue(GlObjectAsDuck).AsInteger
else if Assigned(GlGetCountMethod) then
Result := GlGetCountMethod.Invoke(GlObjectAsDuck, []).AsInteger;
end;
2013-10-30 00:48:23 +01:00
class constructor TDuckTypedList.Create;
begin
GlContext := TRttiContext.Create;
ClearRttiData;
2013-10-30 00:48:23 +01:00
end;
constructor TDuckTypedList.Create(const AObjectAsDuck: TObject; AOwnsObject: Boolean);
2013-10-30 00:48:23 +01:00
begin
inherited Create;
FOwnsObject := AOwnsObject;
ClearRttiData;
GlObjectAsDuck := AObjectAsDuck;
GlAddMethod := GlContext.GetType(AObjectAsDuck.ClassInfo).GetMethod('Add');
if not Assigned(GlAddMethod) then
raise EMVCDuckTypingException.Create('Cannot find method "Add" in the duck object');
GlClearMethod := GlContext.GetType(AObjectAsDuck.ClassInfo).GetMethod('Clear');
if not Assigned(GlClearMethod) then
raise EMVCDuckTypingException.Create('Cannot find method "Clear" in the duck object');
{$IF CompilerVersion >= 23}
GlGetItemMethod := GlContext.GetType(AObjectAsDuck.ClassInfo).GetIndexedProperty('Items').ReadMethod;
2013-10-30 00:48:23 +01:00
{$IFEND}
if not Assigned(GlGetItemMethod) then
GlGetItemMethod := GlContext.GetType(AObjectAsDuck.ClassInfo).GetMethod('GetItem');
if not Assigned(GlGetItemMethod) then
GlGetItemMethod := GlContext.GetType(AObjectAsDuck.ClassInfo).GetMethod('GetElement');
if not Assigned(GlGetItemMethod) then
raise EMVCDuckTypingException.Create('Cannot find method Indexed property "Items" or method "GetItem" or method "GetElement" in the duck object');
GlCountProperty := GlContext.GetType(AObjectAsDuck.ClassInfo).GetProperty('Count');
if not Assigned(GlCountProperty) then
begin
GlGetCountMethod := GlContext.GetType(AObjectAsDuck.ClassInfo).GetMethod('Count');
if not Assigned(GlGetCountMethod) then
raise EMVCDuckTypingException.Create('Cannot find property/method "Count" in the duck object');
2013-10-30 00:48:23 +01:00
end;
end;
class destructor TDuckTypedList.Destroy;
begin
GlContext.Free;
end;
2013-10-30 00:48:23 +01:00
destructor TDuckTypedList.Destroy;
begin
if FOwnsObject and Assigned(GlObjectAsDuck) then
FreeAndNil(GlObjectAsDuck);
2013-10-30 00:48:23 +01:00
inherited;
end;
function TDuckTypedList.GetItem(const AIndex: Integer): TObject;
2013-10-30 00:48:23 +01:00
begin
Result := nil;
if Assigned(GlGetItemMethod) then
Result := GlGetItemMethod.Invoke(GlObjectAsDuck, [AIndex]).AsObject;
2013-10-30 00:48:23 +01:00
end;
function TDuckTypedList.GetOwnsObjects: Boolean;
2013-10-30 00:48:23 +01:00
begin
Result := False;
if Assigned(GlObjectAsDuck) then
Result := TRttiUtils.GetProperty(GlObjectAsDuck, 'OwnsObjects').AsBoolean
2013-10-30 00:48:23 +01:00
end;
class function TDuckTypedList.Wrap(const AObjectAsDuck: TObject): IMVCList;
var
ObjType: TRttiType;
{$IF CompilerVersion >= 23}
IndexedProperty: TRttiIndexedProperty;
{$IFEND}
begin
ObjType := GlContext.GetType(AObjectAsDuck.ClassInfo);
GlAddMethod := ObjType.GetMethod('Add');
if not Assigned(GlAddMethod) then
Exit(nil);
GlClearMethod := ObjType.GetMethod('Clear');
if not Assigned(GlClearMethod) then
Exit(nil);
GlGetItemMethod := nil;
{$IF CompilerVersion >= 23}
IndexedProperty := ObjType.GetIndexedProperty('Items');
if IndexedProperty = nil then
2017-02-09 19:33:59 +01:00
Exit(nil);
GlGetItemMethod := IndexedProperty.ReadMethod;
{$IFEND}
if not Assigned(GlGetItemMethod) then
GlGetItemMethod := ObjType.GetMethod('GetItem');
if not Assigned(GlGetItemMethod) then
GlGetItemMethod := ObjType.GetMethod('GetElement');
if not Assigned(GlGetItemMethod) then
Exit(nil);
GlCountProperty := ObjType.GetProperty('Count');
if not Assigned(GlCountProperty) then
begin
GlGetCountMethod := ObjType.GetMethod('Count');
if not Assigned(GlGetCountMethod) then
Exit(nil);
end;
Result := TDuckTypedList.Create(AObjectAsDuck);
end;
2013-10-30 00:48:23 +01:00
function TDuckTypedList.WrappedObject: TObject;
begin
Result := GlObjectAsDuck;
2013-10-30 00:48:23 +01:00
end;
function WrapAsList(const AObject: TObject; AOwnsObject: Boolean): IMVCList;
2013-10-30 00:48:23 +01:00
begin
try
Result := TDuckTypedList.Create(AObject, AOwnsObject);
2013-10-30 00:48:23 +01:00
except
Result := nil;
end;
end;
procedure TDuckTypedList.QuickSort(AList: IMVCList; L, R: Integer; ACompare: TFunc<TObject, TObject, Integer>);
2013-10-30 00:48:23 +01:00
var
I, J: Integer;
2014-03-07 23:16:33 +01:00
p: TObject;
2013-10-30 00:48:23 +01:00
begin
{ 07/08/2013: This method is based on QuickSort procedure from
Classes.pas, (c) Borland Software Corp.
but modified to be part of TDuckListU unit. It implements the
standard quicksort algorithm,
delegating comparison operation to an anonimous.
The Borland version delegates to a pure function
pointer, which is problematic in some cases. }
repeat
I := L;
J := R;
p := AList.GetItem((L + R) shr 1);
2013-10-30 00:48:23 +01:00
repeat
while ACompare(TObject(AList.GetItem(I)), p) < 0 do
2013-10-30 00:48:23 +01:00
Inc(I);
while ACompare(TObject(AList.GetItem(J)), p) > 0 do
2013-10-30 00:48:23 +01:00
Dec(J);
if I <= J then
2013-10-30 00:48:23 +01:00
begin
TRttiUtils.MethodCall(AList.WrappedObject, 'Exchange', [I, J]);
2013-10-30 00:48:23 +01:00
Inc(I);
Dec(J);
end;
until I > J;
if L < J then
QuickSort(AList, L, J, ACompare);
2013-10-30 00:48:23 +01:00
L := I;
until I >= R;
2013-10-30 00:48:23 +01:00
end;
procedure TDuckTypedList.QuickSort(AList: IMVCList; ACompare: TFunc<TObject, TObject, Integer>);
2013-10-30 00:48:23 +01:00
begin
QuickSort(AList, 0, AList.Count - 1, ACompare);
2013-10-30 00:48:23 +01:00
end;
function CompareValue(const Left, Right: TValue): Integer;
begin
if Left.IsOrdinal then
begin
Result := System.Math.CompareValue(Left.AsOrdinal, Right.AsOrdinal);
end
else if Left.Kind = tkFloat then
2013-10-30 00:48:23 +01:00
begin
Result := System.Math.CompareValue(Left.AsExtended, Right.AsExtended);
end
else if Left.Kind in [tkString, tkUString, tkWString, tkLString] then
2013-10-30 00:48:23 +01:00
begin
Result := CompareText(Left.AsString, Right.AsString);
end
else
begin
Result := 0;
end;
end;
procedure TDuckTypedList.SetOwnsObjects(const AValue: Boolean);
2013-10-30 00:48:23 +01:00
begin
TRttiUtils.SetProperty(GlObjectAsDuck, 'OwnsObjects', AValue);
2013-10-30 00:48:23 +01:00
end;
procedure TDuckTypedList.Sort(const APropertyName: string; AOrder: TSortingType);
2013-10-30 00:48:23 +01:00
begin
if AOrder = soAscending then
2013-10-30 00:48:23 +01:00
QuickSort(self,
function(Left, Right: TObject): Integer
begin
Result := CompareValue(TRttiUtils.GetProperty(Left, APropertyName),
TRttiUtils.GetProperty(Right, APropertyName));
2013-10-30 00:48:23 +01:00
end)
else
QuickSort(self,
function(Left, Right: TObject): Integer
begin
Result := -1 * CompareValue(TRttiUtils.GetProperty(Left, APropertyName),
TRttiUtils.GetProperty(Right, APropertyName));
2013-10-30 00:48:23 +01:00
end);
end;
end.