2016-06-22 17:49:16 +02:00
|
|
|
// ***************************************************************************
|
|
|
|
//
|
|
|
|
// Delphi MVC Framework
|
|
|
|
//
|
|
|
|
// Copyright (c) 2010-2016 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
|
|
|
|
2016-09-30 11:44:11 +02:00
|
|
|
unit MVCFramework.DuckTyping;
|
2013-10-30 00:48:23 +01:00
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
uses
|
|
|
|
RTTI,
|
|
|
|
Classes,
|
|
|
|
// superobject,
|
|
|
|
Generics.Collections,
|
|
|
|
SysUtils,
|
|
|
|
TypInfo;
|
|
|
|
|
|
|
|
type
|
2016-09-29 18:17:12 +02:00
|
|
|
TDuckTypedList = class;
|
2013-10-30 00:48:23 +01:00
|
|
|
|
2016-09-29 18:17:12 +02:00
|
|
|
IList = interface
|
2013-10-30 00:48:23 +01:00
|
|
|
['{2A1BCB3C-17A2-4F8D-B6FB-32B2A1BFE840}']
|
|
|
|
function Add(const Value: TObject): Integer;
|
|
|
|
procedure Clear;
|
|
|
|
function Count: Integer;
|
|
|
|
function GetItem(index: Integer): TObject;
|
|
|
|
end;
|
|
|
|
|
2016-09-29 18:17:12 +02:00
|
|
|
TDuckListEnumerator = class(TEnumerator<TObject>)
|
2013-10-30 00:48:23 +01:00
|
|
|
protected
|
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;
|
|
|
|
|
|
|
|
public
|
|
|
|
constructor Create(ADuckTypedList: TDuckTypedList);
|
|
|
|
end;
|
|
|
|
|
2016-09-29 18:17:12 +02:00
|
|
|
TSortingType = (soAscending, soDescending);
|
2013-10-30 00:48:23 +01:00
|
|
|
|
2016-09-29 18:17:12 +02:00
|
|
|
IWrappedList = interface
|
2013-10-30 00:48:23 +01:00
|
|
|
['{B60AF5A6-7C31-4EAA-8DFB-D8BD3E112EE7}']
|
|
|
|
function Count: Integer;
|
|
|
|
function GetItem(const index: Integer): TObject;
|
|
|
|
procedure Add(const AObject: TObject);
|
|
|
|
procedure Clear;
|
2016-09-29 18:17:12 +02:00
|
|
|
function GetEnumerator: TDuckListEnumerator;
|
2013-10-30 00:48:23 +01:00
|
|
|
function WrappedObject: TObject;
|
2016-09-29 18:17:12 +02:00
|
|
|
procedure Sort(const PropertyName: string;
|
|
|
|
Order: TSortingType = soAscending);
|
2013-10-30 00:48:23 +01:00
|
|
|
function GetOwnsObjects: boolean;
|
|
|
|
procedure SetOwnsObjects(const Value: boolean);
|
|
|
|
property OwnsObjects: boolean read GetOwnsObjects write SetOwnsObjects;
|
|
|
|
end;
|
|
|
|
|
2016-09-29 18:17:12 +02:00
|
|
|
TDuckTypedList = class(TInterfacedObject, IWrappedList)
|
2016-11-18 18:25:09 +01:00
|
|
|
private
|
|
|
|
FOwnsObject: boolean;
|
2013-10-30 00:48:23 +01:00
|
|
|
protected
|
2014-03-07 23:16:33 +01:00
|
|
|
FCTX: TRTTIContext;
|
|
|
|
FObjectAsDuck: TObject;
|
|
|
|
FAddMethod: TRttiMethod;
|
|
|
|
FClearMethod: TRttiMethod;
|
|
|
|
FCountProperty: TRttiProperty;
|
|
|
|
FGetItemMethod: TRttiMethod;
|
2013-10-30 00:48:23 +01:00
|
|
|
FGetCountMethod: TRttiMethod;
|
|
|
|
function Count: Integer;
|
|
|
|
function GetItem(const index: Integer): TObject;
|
|
|
|
procedure Add(const AObject: TObject);
|
|
|
|
procedure Clear;
|
|
|
|
function WrappedObject: TObject;
|
2016-09-29 18:17:12 +02:00
|
|
|
procedure QuickSort(List: IWrappedList; L, R: Integer;
|
|
|
|
SCompare: TFunc<TObject, TObject, Integer>); overload;
|
2013-10-30 00:48:23 +01:00
|
|
|
|
2016-09-29 18:17:12 +02:00
|
|
|
procedure QuickSort(List: IWrappedList;
|
|
|
|
SCompare: TFunc<TObject, TObject, Integer>); overload;
|
|
|
|
procedure Sort(const PropertyName: string;
|
|
|
|
Order: TSortingType = soAscending);
|
2013-10-30 00:48:23 +01:00
|
|
|
|
|
|
|
public
|
2016-11-18 18:25:09 +01:00
|
|
|
constructor Create(AObjectAsDuck: TObject; aOwnsObject: boolean = false);
|
2013-10-30 00:48:23 +01:00
|
|
|
destructor Destroy; override;
|
2016-09-29 18:17:12 +02:00
|
|
|
function GetEnumerator: TDuckListEnumerator;
|
2013-10-30 00:48:23 +01:00
|
|
|
function GetOwnsObjects: boolean;
|
|
|
|
procedure SetOwnsObjects(const Value: boolean);
|
|
|
|
property OwnsObjects: boolean read GetOwnsObjects write SetOwnsObjects;
|
2016-09-29 18:17:12 +02:00
|
|
|
class function CanBeWrappedAsList(const AObjectAsDuck: TObject)
|
|
|
|
: boolean; overload;
|
|
|
|
class function CanBeWrappedAsList(const AInterfaceAsDuck: IInterface)
|
|
|
|
: boolean; overload;
|
2013-10-30 00:48:23 +01:00
|
|
|
end;
|
|
|
|
|
2016-11-18 18:25:09 +01:00
|
|
|
function WrapAsList(const AObject: TObject; aOwnsObject: boolean = false)
|
|
|
|
: IWrappedList;
|
2013-10-30 00:48:23 +01:00
|
|
|
|
|
|
|
implementation
|
|
|
|
|
|
|
|
uses System.Math,
|
2016-09-30 11:44:11 +02:00
|
|
|
MVCFramework.RTTIUtils, MVCFramework.Commons;
|
2013-10-30 00:48:23 +01:00
|
|
|
|
2016-09-29 18:17:12 +02:00
|
|
|
constructor TDuckListEnumerator.Create(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;
|
|
|
|
|
2016-09-29 18:17:12 +02:00
|
|
|
function TDuckListEnumerator.DoGetCurrent: TObject;
|
2013-10-30 00:48:23 +01:00
|
|
|
begin
|
2016-09-29 18:17:12 +02:00
|
|
|
if FPosition > -1 then
|
2013-10-30 00:48:23 +01:00
|
|
|
Result := FDuckTypedList.GetItem(FPosition)
|
|
|
|
else
|
|
|
|
raise Exception.Create('Enumerator error: Call MoveNext first');
|
|
|
|
end;
|
|
|
|
|
2016-09-29 18:17:12 +02:00
|
|
|
function TDuckListEnumerator.DoMoveNext: boolean;
|
2013-10-30 00:48:23 +01:00
|
|
|
begin
|
2016-09-29 18:17:12 +02:00
|
|
|
if FPosition < FDuckTypedList.Count - 1 then
|
2013-10-30 00:48:23 +01:00
|
|
|
begin
|
|
|
|
Inc(FPosition);
|
|
|
|
Result := True;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
Result := false;
|
|
|
|
end;
|
|
|
|
|
2016-09-29 18:17:12 +02:00
|
|
|
function TDuckTypedList.GetEnumerator: TDuckListEnumerator;
|
2013-10-30 00:48:23 +01:00
|
|
|
begin
|
2016-09-29 18:17:12 +02:00
|
|
|
Result := TDuckListEnumerator.Create(self);
|
2013-10-30 00:48:23 +01:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TDuckTypedList.Add(const AObject: TObject);
|
|
|
|
begin
|
|
|
|
FAddMethod.Invoke(FObjectAsDuck, [AObject]);
|
|
|
|
end;
|
|
|
|
|
2016-09-29 18:17:12 +02:00
|
|
|
class function TDuckTypedList.CanBeWrappedAsList(const AObjectAsDuck
|
|
|
|
: TObject): boolean;
|
2013-10-30 00:48:23 +01:00
|
|
|
var
|
|
|
|
FCTX: TRTTIContext;
|
|
|
|
begin
|
2016-09-29 18:17:12 +02:00
|
|
|
Result := (FCTX.GetType(AObjectAsDuck.ClassInfo).GetMethod('Add') <> nil) and
|
|
|
|
(FCTX.GetType(AObjectAsDuck.ClassInfo).GetMethod('Clear') <> nil)
|
2013-10-30 00:48:23 +01:00
|
|
|
|
2014-03-07 23:16:33 +01:00
|
|
|
{$IF CompilerVersion >= 23}
|
2016-09-29 18:17:12 +02:00
|
|
|
and (FCTX.GetType(AObjectAsDuck.ClassInfo).GetIndexedProperty('Items')
|
|
|
|
.ReadMethod <> nil)
|
2013-10-30 00:48:23 +01:00
|
|
|
|
2014-03-07 23:16:33 +01:00
|
|
|
{$IFEND}
|
2016-09-29 18:17:12 +02:00
|
|
|
and ((FCTX.GetType(AObjectAsDuck.ClassInfo).GetMethod('GetItem') <> nil) or
|
|
|
|
(FCTX.GetType(AObjectAsDuck.ClassInfo).GetMethod('GetElement') <> nil)) and
|
|
|
|
(FCTX.GetType(AObjectAsDuck.ClassInfo).GetProperty('Count') <> nil)
|
|
|
|
|
|
|
|
end;
|
|
|
|
|
2016-11-18 18:25:09 +01:00
|
|
|
class function TDuckTypedList.CanBeWrappedAsList(const AInterfaceAsDuck
|
|
|
|
: IInterface): boolean;
|
2016-09-29 18:17:12 +02:00
|
|
|
var
|
|
|
|
FCTX: TRTTIContext;
|
|
|
|
LType: TRttiType;
|
|
|
|
begin
|
|
|
|
LType := FCTX.GetType(TObject(AInterfaceAsDuck).ClassInfo);
|
|
|
|
Result := (LType.GetMethod('Add') <> nil) and
|
|
|
|
(LType.GetMethod('Clear') <> nil)
|
|
|
|
|
|
|
|
{$IF CompilerVersion >= 23}
|
2016-11-18 18:25:09 +01:00
|
|
|
and (LType.GetIndexedProperty('Items').ReadMethod <> nil)
|
2016-09-29 18:17:12 +02:00
|
|
|
|
|
|
|
{$ENDIF}
|
|
|
|
and (LType.GetMethod('GetItem') <> nil) or
|
|
|
|
(LType.GetMethod('GetElement') <> nil) and
|
|
|
|
(LType.GetProperty('Count') <> nil)
|
2013-10-30 00:48:23 +01:00
|
|
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TDuckTypedList.Clear;
|
|
|
|
begin
|
|
|
|
FClearMethod.Invoke(FObjectAsDuck, []);
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TDuckTypedList.Count: Integer;
|
|
|
|
begin
|
|
|
|
if Assigned(FCountProperty) then
|
|
|
|
Result := FCountProperty.GetValue(FObjectAsDuck).AsInteger
|
|
|
|
else
|
|
|
|
Result := FGetCountMethod.Invoke(FObjectAsDuck, []).AsInteger;
|
|
|
|
|
|
|
|
end;
|
|
|
|
|
2016-11-18 18:25:09 +01:00
|
|
|
constructor TDuckTypedList.Create(AObjectAsDuck: TObject; aOwnsObject: boolean);
|
2013-10-30 00:48:23 +01:00
|
|
|
begin
|
|
|
|
inherited Create;
|
2016-11-18 18:25:09 +01:00
|
|
|
FOwnsObject := aOwnsObject;
|
2013-10-30 00:48:23 +01:00
|
|
|
FObjectAsDuck := AObjectAsDuck;
|
|
|
|
FAddMethod := FCTX.GetType(AObjectAsDuck.ClassInfo).GetMethod('Add');
|
|
|
|
if not Assigned(FAddMethod) then
|
2016-09-29 18:17:12 +02:00
|
|
|
raise EMVCException.Create('Cannot find method "Add" in the duck object');
|
2013-10-30 00:48:23 +01:00
|
|
|
FClearMethod := FCTX.GetType(AObjectAsDuck.ClassInfo).GetMethod('Clear');
|
|
|
|
if not Assigned(FClearMethod) then
|
2016-11-18 18:25:09 +01:00
|
|
|
raise EMVCException.Create('Cannot find method "Clear" in the duck object');
|
2013-10-30 00:48:23 +01:00
|
|
|
FGetItemMethod := nil;
|
|
|
|
|
2014-03-07 23:16:33 +01:00
|
|
|
{$IF CompilerVersion >= 23}
|
2016-09-29 18:17:12 +02:00
|
|
|
FGetItemMethod := FCTX.GetType(AObjectAsDuck.ClassInfo)
|
|
|
|
.GetIndexedProperty('Items').ReadMethod;
|
2013-10-30 00:48:23 +01:00
|
|
|
|
2014-03-07 23:16:33 +01:00
|
|
|
{$IFEND}
|
2013-10-30 00:48:23 +01:00
|
|
|
if not Assigned(FGetItemMethod) then
|
2016-09-29 18:17:12 +02:00
|
|
|
FGetItemMethod := FCTX.GetType(AObjectAsDuck.ClassInfo)
|
|
|
|
.GetMethod('GetItem');
|
2013-10-30 00:48:23 +01:00
|
|
|
if not Assigned(FGetItemMethod) then
|
2016-09-29 18:17:12 +02:00
|
|
|
FGetItemMethod := FCTX.GetType(AObjectAsDuck.ClassInfo)
|
|
|
|
.GetMethod('GetElement');
|
2013-10-30 00:48:23 +01:00
|
|
|
if not Assigned(FGetItemMethod) then
|
2016-09-29 18:17:12 +02:00
|
|
|
raise EMVCException.Create
|
2013-10-30 00:48:23 +01:00
|
|
|
('Cannot find method Indexed property "Items" or method "GetItem" or method "GetElement" in the duck object');
|
|
|
|
FCountProperty := FCTX.GetType(AObjectAsDuck.ClassInfo).GetProperty('Count');
|
|
|
|
if not Assigned(FCountProperty) then
|
|
|
|
begin
|
2015-02-16 14:25:09 +01:00
|
|
|
FGetCountMethod := FCTX.GetType(AObjectAsDuck.ClassInfo).GetMethod('Count');
|
2013-10-30 00:48:23 +01:00
|
|
|
if not Assigned(FGetCountMethod) then
|
|
|
|
|
2016-09-29 18:17:12 +02:00
|
|
|
raise EMVCException.Create
|
|
|
|
('Cannot find property/method "Count" in the duck object');
|
2013-10-30 00:48:23 +01:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
destructor TDuckTypedList.Destroy;
|
|
|
|
begin
|
2016-11-18 18:25:09 +01:00
|
|
|
if FOwnsObject then
|
|
|
|
FreeAndNil(FObjectAsDuck);
|
2013-10-30 00:48:23 +01:00
|
|
|
inherited;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TDuckTypedList.GetItem(const index: Integer): TObject;
|
|
|
|
begin
|
|
|
|
Result := FGetItemMethod.Invoke(FObjectAsDuck, [index]).AsObject;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TDuckTypedList.GetOwnsObjects: boolean;
|
|
|
|
begin
|
|
|
|
Result := TRTTIUtils.GetProperty(FObjectAsDuck, 'OwnsObjects').AsBoolean
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TDuckTypedList.WrappedObject: TObject;
|
|
|
|
begin
|
|
|
|
Result := FObjectAsDuck;
|
|
|
|
end;
|
|
|
|
|
2016-11-18 18:25:09 +01:00
|
|
|
function WrapAsList(const AObject: TObject; aOwnsObject: boolean): IWrappedList;
|
2013-10-30 00:48:23 +01:00
|
|
|
begin
|
|
|
|
try
|
2016-11-18 18:25:09 +01:00
|
|
|
Result := TDuckTypedList.Create(AObject, aOwnsObject);
|
2013-10-30 00:48:23 +01:00
|
|
|
except
|
|
|
|
Result := nil;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2016-09-29 18:17:12 +02:00
|
|
|
procedure TDuckTypedList.QuickSort(List: IWrappedList; L, R: Integer;
|
|
|
|
SCompare: 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;
|
2016-09-29 18:17:12 +02:00
|
|
|
p := List.GetItem((L + R) shr 1);
|
2013-10-30 00:48:23 +01:00
|
|
|
repeat
|
2016-09-29 18:17:12 +02:00
|
|
|
while SCompare(TObject(List.GetItem(I)), p) < 0 do
|
2013-10-30 00:48:23 +01:00
|
|
|
Inc(I);
|
2016-09-29 18:17:12 +02:00
|
|
|
while SCompare(TObject(List.GetItem(J)), p) > 0 do
|
2013-10-30 00:48:23 +01:00
|
|
|
Dec(J);
|
2016-09-29 18:17:12 +02:00
|
|
|
if I <= J then
|
2013-10-30 00:48:23 +01:00
|
|
|
begin
|
|
|
|
TRTTIUtils.MethodCall(List.WrappedObject, 'Exchange', [I, J]);
|
|
|
|
Inc(I);
|
|
|
|
Dec(J);
|
|
|
|
end;
|
2016-09-29 18:17:12 +02:00
|
|
|
until I > J;
|
|
|
|
if L < J then
|
2013-10-30 00:48:23 +01:00
|
|
|
QuickSort(List, L, J, SCompare);
|
|
|
|
L := I;
|
2016-09-29 18:17:12 +02:00
|
|
|
until I >= R;
|
2013-10-30 00:48:23 +01:00
|
|
|
end;
|
|
|
|
|
2016-09-29 18:17:12 +02:00
|
|
|
procedure TDuckTypedList.QuickSort(List: IWrappedList;
|
|
|
|
SCompare: TFunc<TObject, TObject, Integer>);
|
2013-10-30 00:48:23 +01:00
|
|
|
begin
|
2016-09-29 18:17:12 +02:00
|
|
|
QuickSort(List, 0, List.Count - 1, SCompare);
|
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
|
2016-09-29 18:17:12 +02:00
|
|
|
else if Left.Kind = tkFloat then
|
2013-10-30 00:48:23 +01:00
|
|
|
begin
|
|
|
|
Result := System.Math.CompareValue(Left.AsExtended, Right.AsExtended);
|
|
|
|
end
|
2015-02-16 14:25:09 +01:00
|
|
|
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 Value: boolean);
|
|
|
|
begin
|
|
|
|
TRTTIUtils.SetProperty(FObjectAsDuck, 'OwnsObjects', Value);
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TDuckTypedList.Sort(const PropertyName: string; Order: TSortingType);
|
|
|
|
begin
|
2016-09-29 18:17:12 +02:00
|
|
|
if Order = soAscending then
|
2013-10-30 00:48:23 +01:00
|
|
|
QuickSort(self,
|
|
|
|
function(Left, Right: TObject): Integer
|
|
|
|
begin
|
2016-09-29 18:17:12 +02:00
|
|
|
Result := CompareValue(TRTTIUtils.GetProperty(Left, PropertyName),
|
|
|
|
TRTTIUtils.GetProperty(Right, PropertyName));
|
2013-10-30 00:48:23 +01:00
|
|
|
end)
|
|
|
|
else
|
|
|
|
QuickSort(self,
|
|
|
|
function(Left, Right: TObject): Integer
|
|
|
|
begin
|
2016-09-29 18:17:12 +02:00
|
|
|
Result := -1 * CompareValue(TRTTIUtils.GetProperty(Left, PropertyName),
|
|
|
|
TRTTIUtils.GetProperty(Right, PropertyName));
|
2013-10-30 00:48:23 +01:00
|
|
|
end);
|
|
|
|
end;
|
|
|
|
|
|
|
|
end.
|