diff --git a/README.md b/README.md
index bc411dc2..83b2a254 100644
--- a/README.md
+++ b/README.md
@@ -1423,16 +1423,22 @@ The current beta release is named 3.2.3-radium-beta. If you want to stay on the-
- mid-air-collision handling now uses SHA1 instead of MD5
-- Added `MVCFramework.Commons.MVC_HTTP_STATUS_CODES` const array containing all the HTTP status codes with its `ReasonString`
+- Added `MVCFramework.Commons.MVC_HTTP_STATUS_CODES` const array containing all the HTTP status codes with its `ReasonString`.
+
+- Support for `TObject` descendants in JSONRPC APIs (not only for JSONObject and JSONArray).
- New global configuration variable `MVCSerializeNulls`.
When MVCSerializeNulls = True (default) empty nullables and nil are serialized as json null.
When MVCSerializeNulls = False empty nullables and nil are not serialized at all.
-- Nullable types now have `Equal` support and a better "equality check" strategy.
+- Nullable types now have `Equal` method support, the new method `TryHasValue(out Value)` works like `HasValue` but returns the contained value if present. Also there is a better "equality check" strategy.
- Unit tests now are always executed for Win32 and Win64 bit (both client and server).
+- Added `TMVCActiveRecord.Refresh` method
+
+- Unit test suites generates one NUnit XML output file for each platform
+
- New built-in profiler (usable with Delphi 10.4+) - to profile a block of code, write the following
```delphi
diff --git a/samples/activerecord_showcase/MainFormU.dfm b/samples/activerecord_showcase/MainFormU.dfm
index a8aac854..5d1aeb54 100644
--- a/samples/activerecord_showcase/MainFormU.dfm
+++ b/samples/activerecord_showcase/MainFormU.dfm
@@ -258,6 +258,15 @@ object MainForm: TMainForm
TabOrder = 24
OnClick = btnSpeedClick
end
+ object btnRefresh: TButton
+ Left = 144
+ Top = 442
+ Width = 121
+ Height = 34
+ Caption = 'Manual Refresh'
+ TabOrder = 25
+ OnClick = btnRefreshClick
+ end
object FDConnection1: TFDConnection
Left = 312
Top = 40
diff --git a/samples/activerecord_showcase/MainFormU.pas b/samples/activerecord_showcase/MainFormU.pas
index f75c19f9..53e1ea89 100644
--- a/samples/activerecord_showcase/MainFormU.pas
+++ b/samples/activerecord_showcase/MainFormU.pas
@@ -57,6 +57,7 @@ type
btnOOP: TButton;
btnReadOnly: TButton;
btnSpeed: TButton;
+ btnRefresh: TButton;
procedure btnCRUDClick(Sender: TObject);
procedure btnInheritanceClick(Sender: TObject);
procedure btnMultiThreadingClick(Sender: TObject);
@@ -84,6 +85,7 @@ type
procedure btnOOPClick(Sender: TObject);
procedure btnReadOnlyClick(Sender: TObject);
procedure btnSpeedClick(Sender: TObject);
+ procedure btnRefreshClick(Sender: TObject);
private
procedure Log(const Value: string);
procedure LoadCustomers;
@@ -1467,6 +1469,44 @@ begin
end;
end;
+procedure TMainForm.btnRefreshClick(Sender: TObject);
+var
+ lCustomer: TCustomer;
+ lID: Integer;
+begin
+ Log('** Refresh test');
+ lCustomer := TCustomer.Create;
+ try
+ Log('Entity ' + TCustomer.ClassName + ' is mapped to table ' + lCustomer.TableName);
+ lCustomer.CompanyName := 'Google Inc.';
+ lCustomer.City := 'Montain View, CA';
+ lCustomer.Note := 'Μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος οὐλομένην 😁';
+ lCustomer.Insert;
+ Assert('Montain View, CA' = lCustomer.City);
+ Assert('Μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος οὐλομένην 😁' = lCustomer.Note);
+ lCustomer.City := '';
+ lCustomer.Note := '';
+ Log('Refreshing the customer');
+ lCustomer.Refresh;
+ Assert('Montain View, CA' = lCustomer.City);
+ Assert('Μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος οὐλομένην 😁' = lCustomer.Note);
+ lID := lCustomer.ID;
+ finally
+ lCustomer.Free;
+ end;
+
+ lCustomer := TCustomer.Create;
+ try
+ Log('Loading customer using Refresh');
+ lCustomer.ID := lID;
+ lCustomer.Refresh;
+ Assert('Montain View, CA' = lCustomer.City);
+ Assert('Μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος οὐλομένην 😁' = lCustomer.Note);
+ finally
+ lCustomer.Free;
+ end;
+end;
+
procedure TMainForm.btnValidationClick(Sender: TObject);
var
lCustomer: TCustomerWithLogic;
diff --git a/samples/renders/renders.dproj b/samples/renders/renders.dproj
index 64c973ee..a8839b78 100644
--- a/samples/renders/renders.dproj
+++ b/samples/renders/renders.dproj
@@ -220,12 +220,12 @@
true
+
true
-
diff --git a/sources/MVCFramework.ActiveRecord.pas b/sources/MVCFramework.ActiveRecord.pas
index a11b361b..e03bb5da 100644
--- a/sources/MVCFramework.ActiveRecord.pas
+++ b/sources/MVCFramework.ActiveRecord.pas
@@ -195,7 +195,6 @@ type
fDefaultRQLFilter: string;
fMap: TFieldsMap;
fPrimaryKey: TRTTIField;
- //fBackendDriver: string;
fMapping: TMVCFieldsMapping;
fPropsAttributes: TArray;
fProps: TArray;
@@ -224,25 +223,7 @@ type
procedure SetAttributes(const AttrName: string; const Value: TValue);
function GetTableName: string;
protected
-// fPrimaryKeyFieldName: string;
-// fPrimaryKeyOptions: TMVCActiveRecordFieldOptions;
-// fPrimaryKeySequenceName: string;
-// fPrimaryKeyFieldType: TFieldType;
-// fEntityAllowedActions: TMVCEntityActions;
-// fRTTIType: TRttiInstanceType;
-// fObjAttributes: TArray;
-// fTableName: string;
-// fDefaultRQLFilter: string;
-// fMap: TFieldsMap;
-// fPrimaryKey: TRTTIField;
fBackendDriver: string;
-// fMapping: TMVCFieldsMapping;
-// fPropsAttributes: TArray;
-// fProps: TArray;
-
-// fPartitionInfoInternal: TPartitionInfo;
-// fPartitionClause: String;
-
fTableMap: TMVCTableMap;
function GetPartitionInfo: TPartitionInfo;
function GetBackEnd: string;
@@ -280,6 +261,7 @@ type
class function GetByPK(aActiveRecord: TMVCActiveRecord; const aValue: string; const aFieldType: TFieldType;
const RaiseExceptionIfNotFound: Boolean): TMVCActiveRecord; overload;
+
// load events
///
/// Called everywhere before persist object into database
@@ -364,6 +346,10 @@ type
/// Executes an Insert (pk is null) or an Update (pk is not null)
///
procedure Store;
+ ///
+ /// Reload the current instance from database if the primary key is not empty.
+ ///
+ procedure Refresh; virtual;
function CheckAction(const aEntityAction: TMVCEntityAction;
const aRaiseException: Boolean = True): Boolean;
procedure Insert;
@@ -2381,6 +2367,29 @@ begin
// do nothing
end;
+procedure TMVCActiveRecord.Refresh;
+begin
+ if not GetPK.IsEmpty then
+ begin
+ case GetPrimaryKeyFieldType of
+ ftLargeInt: begin
+ LoadByPK(GetPK.AsInt64);
+ end;
+ ftInteger: begin
+ LoadByPK(GetPK.AsInteger);
+ end;
+ ftString: begin
+ LoadByPK(GetPK.AsString);
+ end;
+ ftGuid: begin
+ LoadByPK(GetPK.AsType);
+ end;
+ else
+ raise EMVCActiveRecord.Create('Unknown primary key type');
+ end;
+ end;
+end;
+
procedure TMVCActiveRecord.RemoveChildren(const ChildObject: TObject);
begin
if fChildren <> nil then
@@ -2885,51 +2894,35 @@ begin
begin
if Value.IsType() then
begin
- Result := Value.AsType().HasValue;
- if Result then
- Value := Value.AsType().Value;
+ Result := Value.AsType().TryHasValue(Value);
end
else if Value.IsType() then
begin
- Result := Value.AsType().HasValue;
- if Result then
- Value := Value.AsType().Value;
+ Result := Value.AsType().TryHasValue(Value)
end
else if Value.IsType() then
begin
- Result := Value.AsType().HasValue;
- if Result then
- Value := Value.AsType().Value;
+ Result := Value.AsType().TryHasValue(Value)
end
else if Value.IsType() then
begin
- Result := Value.AsType().HasValue;
- if Result then
- Value := Value.AsType().Value;
+ Result := Value.AsType().TryHasValue(Value)
end
else if Value.IsType() then
begin
- Result := Value.AsType().HasValue;
- if Result then
- Value := Value.AsType().Value;
+ Result := Value.AsType().TryHasValue(Value)
end
else if Value.IsType() then
begin
- Result := Value.AsType().HasValue;
- if Result then
- Value := Value.AsType().Value;
+ Result := Value.AsType().TryHasValue(Value)
end
else if Value.IsType() then
begin
- Result := Value.AsType().HasValue;
- if Result then
- Value := Value.AsType().Value;
+ Result := Value.AsType().TryHasValue(Value)
end
else if Value.IsType() then
begin
- Result := Value.AsType().HasValue;
- if Result then
- Value := TValue.From(Value.AsType().Value);
+ Result := Value.AsType().TryHasValue(Value)
end
else
raise EMVCActiveRecord.Create
diff --git a/sources/MVCFramework.Nullables.pas b/sources/MVCFramework.Nullables.pas
index 3b91c191..ea5a79cd 100644
--- a/sources/MVCFramework.Nullables.pas
+++ b/sources/MVCFramework.Nullables.pas
@@ -32,7 +32,7 @@ unit MVCFramework.Nullables;
interface
uses
- System.SysUtils, System.Classes, System.TypInfo;
+ System.SysUtils, System.Classes, System.TypInfo, System.RTTI;
type
EMVCNullable = class(Exception)
@@ -56,7 +56,7 @@ type
class operator Implicit(const Value: String): NullableString;
class operator Implicit(const Value: NullableString): String;
class operator Implicit(const Value: Pointer): NullableString;
- class operator Equal(LeftValue: NullableString; RightValue: NullableString) : Boolean;
+ class operator Equal(LeftValue: NullableString; RightValue: NullableString) : Boolean;
///
///Returns `True` if the NullableString contains a value
///
@@ -82,6 +82,14 @@ type
///
function Equals(const Value: NullableString): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: String): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: String read GetValue write SetValue;
@@ -104,7 +112,7 @@ type
class operator Implicit(const Value: Currency): NullableCurrency;
class operator Implicit(const Value: NullableCurrency): Currency;
class operator Implicit(const Value: Pointer): NullableCurrency;
- class operator Equal(LeftValue: NullableCurrency; RightValue: NullableCurrency) : Boolean;
+ class operator Equal(LeftValue: NullableCurrency; RightValue: NullableCurrency) : Boolean;
///
///Returns `True` if the NullableCurrency contains a value
///
@@ -130,6 +138,14 @@ type
///
function Equals(const Value: NullableCurrency): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: Currency): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: Currency read GetValue write SetValue;
@@ -152,7 +168,7 @@ type
class operator Implicit(const Value: Boolean): NullableBoolean;
class operator Implicit(const Value: NullableBoolean): Boolean;
class operator Implicit(const Value: Pointer): NullableBoolean;
- class operator Equal(LeftValue: NullableBoolean; RightValue: NullableBoolean) : Boolean;
+ class operator Equal(LeftValue: NullableBoolean; RightValue: NullableBoolean) : Boolean;
///
///Returns `True` if the NullableBoolean contains a value
///
@@ -178,6 +194,14 @@ type
///
function Equals(const Value: NullableBoolean): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: Boolean): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: Boolean read GetValue write SetValue;
@@ -200,7 +224,7 @@ type
class operator Implicit(const Value: TDate): NullableTDate;
class operator Implicit(const Value: NullableTDate): TDate;
class operator Implicit(const Value: Pointer): NullableTDate;
- class operator Equal(LeftValue: NullableTDate; RightValue: NullableTDate) : Boolean;
+ class operator Equal(LeftValue: NullableTDate; RightValue: NullableTDate) : Boolean;
///
///Returns `True` if the NullableTDate contains a value
///
@@ -226,6 +250,14 @@ type
///
function Equals(const Value: NullableTDate): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TDate): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: TDate read GetValue write SetValue;
@@ -248,7 +280,7 @@ type
class operator Implicit(const Value: TTime): NullableTTime;
class operator Implicit(const Value: NullableTTime): TTime;
class operator Implicit(const Value: Pointer): NullableTTime;
- class operator Equal(LeftValue: NullableTTime; RightValue: NullableTTime) : Boolean;
+ class operator Equal(LeftValue: NullableTTime; RightValue: NullableTTime) : Boolean;
///
///Returns `True` if the NullableTTime contains a value
///
@@ -274,6 +306,14 @@ type
///
function Equals(const Value: NullableTTime): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TTime): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: TTime read GetValue write SetValue;
@@ -296,7 +336,7 @@ type
class operator Implicit(const Value: TDateTime): NullableTDateTime;
class operator Implicit(const Value: NullableTDateTime): TDateTime;
class operator Implicit(const Value: Pointer): NullableTDateTime;
- class operator Equal(LeftValue: NullableTDateTime; RightValue: NullableTDateTime) : Boolean;
+ class operator Equal(LeftValue: NullableTDateTime; RightValue: NullableTDateTime) : Boolean;
///
///Returns `True` if the NullableTDateTime contains a value
///
@@ -322,6 +362,14 @@ type
///
function Equals(const Value: NullableTDateTime): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TDateTime): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: TDateTime read GetValue write SetValue;
@@ -344,7 +392,7 @@ type
class operator Implicit(const Value: Single): NullableSingle;
class operator Implicit(const Value: NullableSingle): Single;
class operator Implicit(const Value: Pointer): NullableSingle;
- class operator Equal(LeftValue: NullableSingle; RightValue: NullableSingle) : Boolean;
+ class operator Equal(LeftValue: NullableSingle; RightValue: NullableSingle) : Boolean;
///
///Returns `True` if the NullableSingle contains a value
///
@@ -370,6 +418,14 @@ type
///
function Equals(const Value: NullableSingle): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: Single): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: Single read GetValue write SetValue;
@@ -392,7 +448,7 @@ type
class operator Implicit(const Value: Double): NullableDouble;
class operator Implicit(const Value: NullableDouble): Double;
class operator Implicit(const Value: Pointer): NullableDouble;
- class operator Equal(LeftValue: NullableDouble; RightValue: NullableDouble) : Boolean;
+ class operator Equal(LeftValue: NullableDouble; RightValue: NullableDouble) : Boolean;
///
///Returns `True` if the NullableDouble contains a value
///
@@ -418,6 +474,14 @@ type
///
function Equals(const Value: NullableDouble): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: Double): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: Double read GetValue write SetValue;
@@ -440,7 +504,7 @@ type
class operator Implicit(const Value: Extended): NullableExtended;
class operator Implicit(const Value: NullableExtended): Extended;
class operator Implicit(const Value: Pointer): NullableExtended;
- class operator Equal(LeftValue: NullableExtended; RightValue: NullableExtended) : Boolean;
+ class operator Equal(LeftValue: NullableExtended; RightValue: NullableExtended) : Boolean;
///
///Returns `True` if the NullableExtended contains a value
///
@@ -466,6 +530,14 @@ type
///
function Equals(const Value: NullableExtended): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: Extended): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: Extended read GetValue write SetValue;
@@ -488,7 +560,7 @@ type
class operator Implicit(const Value: Int16): NullableInt16;
class operator Implicit(const Value: NullableInt16): Int16;
class operator Implicit(const Value: Pointer): NullableInt16;
- class operator Equal(LeftValue: NullableInt16; RightValue: NullableInt16) : Boolean;
+ class operator Equal(LeftValue: NullableInt16; RightValue: NullableInt16) : Boolean;
///
///Returns `True` if the NullableInt16 contains a value
///
@@ -514,6 +586,14 @@ type
///
function Equals(const Value: NullableInt16): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: Int16): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: Int16 read GetValue write SetValue;
@@ -536,7 +616,7 @@ type
class operator Implicit(const Value: UInt16): NullableUInt16;
class operator Implicit(const Value: NullableUInt16): UInt16;
class operator Implicit(const Value: Pointer): NullableUInt16;
- class operator Equal(LeftValue: NullableUInt16; RightValue: NullableUInt16) : Boolean;
+ class operator Equal(LeftValue: NullableUInt16; RightValue: NullableUInt16) : Boolean;
///
///Returns `True` if the NullableUInt16 contains a value
///
@@ -562,6 +642,14 @@ type
///
function Equals(const Value: NullableUInt16): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: UInt16): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: UInt16 read GetValue write SetValue;
@@ -584,7 +672,7 @@ type
class operator Implicit(const Value: Int32): NullableInt32;
class operator Implicit(const Value: NullableInt32): Int32;
class operator Implicit(const Value: Pointer): NullableInt32;
- class operator Equal(LeftValue: NullableInt32; RightValue: NullableInt32) : Boolean;
+ class operator Equal(LeftValue: NullableInt32; RightValue: NullableInt32) : Boolean;
///
///Returns `True` if the NullableInt32 contains a value
///
@@ -610,6 +698,14 @@ type
///
function Equals(const Value: NullableInt32): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: Int32): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: Int32 read GetValue write SetValue;
@@ -632,7 +728,7 @@ type
class operator Implicit(const Value: UInt32): NullableUInt32;
class operator Implicit(const Value: NullableUInt32): UInt32;
class operator Implicit(const Value: Pointer): NullableUInt32;
- class operator Equal(LeftValue: NullableUInt32; RightValue: NullableUInt32) : Boolean;
+ class operator Equal(LeftValue: NullableUInt32; RightValue: NullableUInt32) : Boolean;
///
///Returns `True` if the NullableUInt32 contains a value
///
@@ -658,6 +754,14 @@ type
///
function Equals(const Value: NullableUInt32): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: UInt32): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: UInt32 read GetValue write SetValue;
@@ -680,7 +784,7 @@ type
class operator Implicit(const Value: Int64): NullableInt64;
class operator Implicit(const Value: NullableInt64): Int64;
class operator Implicit(const Value: Pointer): NullableInt64;
- class operator Equal(LeftValue: NullableInt64; RightValue: NullableInt64) : Boolean;
+ class operator Equal(LeftValue: NullableInt64; RightValue: NullableInt64) : Boolean;
///
///Returns `True` if the NullableInt64 contains a value
///
@@ -706,6 +810,14 @@ type
///
function Equals(const Value: NullableInt64): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: Int64): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: Int64 read GetValue write SetValue;
@@ -728,7 +840,7 @@ type
class operator Implicit(const Value: UInt64): NullableUInt64;
class operator Implicit(const Value: NullableUInt64): UInt64;
class operator Implicit(const Value: Pointer): NullableUInt64;
- class operator Equal(LeftValue: NullableUInt64; RightValue: NullableUInt64) : Boolean;
+ class operator Equal(LeftValue: NullableUInt64; RightValue: NullableUInt64) : Boolean;
///
///Returns `True` if the NullableUInt64 contains a value
///
@@ -754,6 +866,14 @@ type
///
function Equals(const Value: NullableUInt64): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: UInt64): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: UInt64 read GetValue write SetValue;
@@ -776,7 +896,7 @@ type
class operator Implicit(const Value: TGUID): NullableTGUID;
class operator Implicit(const Value: NullableTGUID): TGUID;
class operator Implicit(const Value: Pointer): NullableTGUID;
- class operator Equal(LeftValue: NullableTGUID; RightValue: NullableTGUID) : Boolean;
+ class operator Equal(LeftValue: NullableTGUID; RightValue: NullableTGUID) : Boolean;
///
///Returns `True` if the NullableTGUID contains a value
///
@@ -802,6 +922,14 @@ type
///
function Equals(const Value: NullableTGUID): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TGUID): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: TGUID read GetValue write SetValue;
@@ -845,6 +973,25 @@ begin
end;
end;
+function NullableString.TryHasValue(out Value: String): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableString.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableString.Clear;
begin
SetNull;
@@ -935,6 +1082,25 @@ begin
end;
end;
+function NullableCurrency.TryHasValue(out Value: Currency): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableCurrency.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableCurrency.Clear;
begin
SetNull;
@@ -1025,6 +1191,25 @@ begin
end;
end;
+function NullableBoolean.TryHasValue(out Value: Boolean): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableBoolean.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableBoolean.Clear;
begin
SetNull;
@@ -1115,6 +1300,25 @@ begin
end;
end;
+function NullableTDate.TryHasValue(out Value: TDate): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableTDate.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableTDate.Clear;
begin
SetNull;
@@ -1205,6 +1409,25 @@ begin
end;
end;
+function NullableTTime.TryHasValue(out Value: TTime): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableTTime.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableTTime.Clear;
begin
SetNull;
@@ -1295,6 +1518,25 @@ begin
end;
end;
+function NullableTDateTime.TryHasValue(out Value: TDateTime): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableTDateTime.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableTDateTime.Clear;
begin
SetNull;
@@ -1385,6 +1627,25 @@ begin
end;
end;
+function NullableSingle.TryHasValue(out Value: Single): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableSingle.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableSingle.Clear;
begin
SetNull;
@@ -1475,6 +1736,25 @@ begin
end;
end;
+function NullableDouble.TryHasValue(out Value: Double): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableDouble.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableDouble.Clear;
begin
SetNull;
@@ -1565,6 +1845,25 @@ begin
end;
end;
+function NullableExtended.TryHasValue(out Value: Extended): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableExtended.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableExtended.Clear;
begin
SetNull;
@@ -1655,6 +1954,25 @@ begin
end;
end;
+function NullableInt16.TryHasValue(out Value: Int16): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableInt16.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableInt16.Clear;
begin
SetNull;
@@ -1744,6 +2062,25 @@ begin
end;
end;
+function NullableUInt16.TryHasValue(out Value: UInt16): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableUInt16.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableUInt16.Clear;
begin
SetNull;
@@ -1833,6 +2170,25 @@ begin
end;
end;
+function NullableInt32.TryHasValue(out Value: Int32): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableInt32.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableInt32.Clear;
begin
SetNull;
@@ -1922,6 +2278,25 @@ begin
end;
end;
+function NullableUInt32.TryHasValue(out Value: UInt32): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableUInt32.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableUInt32.Clear;
begin
SetNull;
@@ -2011,6 +2386,25 @@ begin
end;
end;
+function NullableInt64.TryHasValue(out Value: Int64): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableInt64.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableInt64.Clear;
begin
SetNull;
@@ -2100,6 +2494,25 @@ begin
end;
end;
+function NullableUInt64.TryHasValue(out Value: UInt64): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableUInt64.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableUInt64.Clear;
begin
SetNull;
@@ -2189,6 +2602,25 @@ begin
end;
end;
+function NullableTGUID.TryHasValue(out Value: TGUID): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function NullableTGUID.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From(fValue);
+ end;
+end;
+
+
procedure NullableTGUID.Clear;
begin
SetNull;
diff --git a/sources/MVCFramework.Nullables.pas.template b/sources/MVCFramework.Nullables.pas.template
index 0c0eed4d..ff9efc3e 100644
--- a/sources/MVCFramework.Nullables.pas.template
+++ b/sources/MVCFramework.Nullables.pas.template
@@ -32,7 +32,7 @@ unit MVCFramework.Nullables;
interface
uses
- System.SysUtils, System.Classes, System.TypInfo;
+ System.SysUtils, System.Classes, System.TypInfo, System.RTTI;
type
EMVCNullable = class(Exception)
@@ -53,7 +53,7 @@ type
class operator Implicit(const Value: $TYPE$): Nullable$TYPE$;
class operator Implicit(const Value: Nullable$TYPE$): $TYPE$;
class operator Implicit(const Value: Pointer): Nullable$TYPE$;
- class operator Equal(LeftValue: Nullable$TYPE$; RightValue: Nullable$TYPE$) : Boolean;
+ class operator Equal(LeftValue: Nullable$TYPE$; RightValue: Nullable$TYPE$) : Boolean;
///
///Returns `True` if the Nullable$TYPE$ contains a value
///
@@ -79,6 +79,14 @@ type
///
function Equals(const Value: Nullable$TYPE$): Boolean;
///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: $TYPE$): Boolean; overload;
+ ///
+ ///Returns true if the nullable contains a value and returns the contained value in the out Value parameter.
+ ///
+ function TryHasValue(out Value: TValue): Boolean; overload;
+ ///
///Returns the value stored or raises exception if no value is stored
///
property Value: $TYPE$ read GetValue write SetValue;
@@ -103,6 +111,25 @@ begin
end;
end;
+function Nullable$TYPE$.TryHasValue(out Value: $TYPE$): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := fValue;
+ end;
+end;
+
+function Nullable$TYPE$.TryHasValue(out Value: TValue): Boolean;
+begin
+ Result := HasValue;
+ if Result then
+ begin
+ Value := TValue.From<$TYPE$>(fValue);
+ end;
+end;
+
+
procedure Nullable$TYPE$.Clear;
begin
SetNull;
diff --git a/unittests/general/Several/ActiveRecordTestsU.pas b/unittests/general/Several/ActiveRecordTestsU.pas
index f3726784..d72553e7 100644
--- a/unittests/general/Several/ActiveRecordTestsU.pas
+++ b/unittests/general/Several/ActiveRecordTestsU.pas
@@ -52,6 +52,8 @@ type
[Test]
procedure TestCRUD;
[Test]
+ procedure TestRefresh;
+ [Test]
procedure Test_ISSUE485;
[Test]
procedure TestDeleteIfNotFound;
@@ -1488,8 +1490,7 @@ begin
CreateACustomer('New York 1', 'New York', 5);
CreateACustomer('Toyko 1', 'Tokyo', 4);
- var
- lRomeCustomer := TMVCActiveRecord.SelectOneByRQL('contains(CompanyName,"1")');
+ var lRomeCustomer := TMVCActiveRecord.SelectOneByRQL('contains(CompanyName,"1")');
try
Assert.IsNotNull(lRomeCustomer);
finally
@@ -1513,6 +1514,48 @@ begin
end;
end;
+procedure TTestActiveRecordBase.TestRefresh;
+var
+ lCustomer: TCustomer;
+ lID: Integer;
+begin
+ Assert.AreEqual(Int64(0), TMVCActiveRecord.Count());
+ lCustomer := TCustomer.Create;
+ try
+ lCustomer.CompanyName := 'bit Time Professionals';
+ lCustomer.City := 'Rome, IT';
+ lCustomer.Note := 'note1';
+ lCustomer.CreationTime := Time;
+ lCustomer.CreationDate := Date;
+ lCustomer.ID := -1; { don't be fooled by the default! }
+ lCustomer.Insert;
+ lID := lCustomer.ID;
+ Assert.AreEqual(1, lID);
+ lCustomer.CompanyName.Clear;
+ lCustomer.City := '';
+ lCustomer.Note := '';
+ lCustomer.CreationTime := 0;
+ lCustomer.CreationDate := 0;
+ lCustomer.Refresh;
+ Assert.AreEqual('bit Time Professionals', lCustomer.CompanyName.ValueOrDefault);
+ Assert.AreEqual('Rome, IT', lCustomer.City);
+ Assert.AreEqual('note1', lCustomer.Note);
+ finally
+ lCustomer.Free;
+ end;
+
+ lCustomer := TCustomer.Create;
+ try
+ lCustomer.ID := lID;
+ lCustomer.Refresh;
+ Assert.AreEqual('bit Time Professionals', lCustomer.CompanyName.ValueOrDefault);
+ Assert.AreEqual('Rome, IT', lCustomer.City);
+ Assert.AreEqual('note1', lCustomer.Note);
+ finally
+ lCustomer.Free;
+ end;
+end;
+
procedure TTestActiveRecordBase.TestRQL;
var
lCustomers: TObjectList;
diff --git a/unittests/general/Several/DMVCFrameworkTests.dpr b/unittests/general/Several/DMVCFrameworkTests.dpr
index 64cdc3a2..1701ad2d 100644
--- a/unittests/general/Several/DMVCFrameworkTests.dpr
+++ b/unittests/general/Several/DMVCFrameworkTests.dpr
@@ -8,9 +8,11 @@ program DMVCFrameworkTests;
uses
System.SysUtils,
+ System.IOUtils,
DUnitX.TestFramework,
{$IFDEF CONSOLE_TESTRUNNER}
DUnitX.Loggers.Console,
+ DUnitX.Loggers.XML.NUnit,
{$ENDIF }
{$IFDEF TESTINSIGHT}
TestInsight.DUnitX,
@@ -83,6 +85,7 @@ var
runner: ITestRunner;
results: IRunResults;
logger: ITestLogger;
+ OutputNUnitFolder: String;
begin
try
// Check command line options, will exit if invalid
@@ -95,9 +98,25 @@ begin
// Log to the console window
logger := TDUnitXConsoleLogger.Create(True);
runner.AddLogger(logger);
+
// Generate an NUnit compatible XML File
- // nunitLogger := TDUnitXXMLNUnitFileLogger.Create(TDUnitX.Options.XMLOutputFile);
- // runner.AddLogger(nunitLogger);
+ if TDUnitX.Options.XMLOutputFile.IsEmpty then
+ begin
+ OutputNUnitFolder := TPath.Combine(
+ TDirectory.GetParent(TDirectory.GetParent(TDirectory.GetParent(AppPath))), 'UnitTestReports');
+ TDirectory.CreateDirectory(OutputNUnitFolder);
+ {$if defined(win32)}
+ TDUnitX.Options.XMLOutputFile := TPath.Combine(OutputNUnitFolder,'dmvcframework_nunit_win32.xml');
+ {$endif}
+ {$if defined(win64)}
+ TDUnitX.Options.XMLOutputFile := TPath.Combine(OutputNUnitFolder, 'dmvcframework_nunit_win64.xml');
+ {$endif}
+ {$if defined(linux64)}
+ TDUnitX.Options.XMLOutputFile := TPath.Combine(OutputNUnitFolder, 'dmvcframework_nunit_linux64.xml');
+ {$endif}
+ end;
+
+ runner.AddLogger(TDUnitXXMLNUnitFileLogger.Create(TDUnitX.Options.XMLOutputFile));
runner.FailsOnNoAsserts := False; // When true, Assertions must be made during tests;
// Run tests
diff --git a/unittests/general/Several/DMVCFrameworkTests.dproj b/unittests/general/Several/DMVCFrameworkTests.dproj
index cc53845e..a9d0b297 100644
--- a/unittests/general/Several/DMVCFrameworkTests.dproj
+++ b/unittests/general/Several/DMVCFrameworkTests.dproj
@@ -236,7 +236,6 @@
- dfm
TWebModule
diff --git a/unittests/general/UnitTestReports/dmvcframework_nunit_win32.xml b/unittests/general/UnitTestReports/dmvcframework_nunit_win32.xml
new file mode 100644
index 00000000..4f249820
--- /dev/null
+++ b/unittests/general/UnitTestReports/dmvcframework_nunit_win32.xml
@@ -0,0 +1,1001 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/unittests/general/UnitTestReports/dmvcframework_nunit_win64.xml b/unittests/general/UnitTestReports/dmvcframework_nunit_win64.xml
new file mode 100644
index 00000000..77a6c6ac
--- /dev/null
+++ b/unittests/general/UnitTestReports/dmvcframework_nunit_win64.xml
@@ -0,0 +1,997 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+