Quick.Options.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. { ***************************************************************************
  2. Copyright (c) 2015-2020 Kike Pérez
  3. Unit : Quick.Options
  4. Description : Configuration group settings
  5. Author : Kike Pérez
  6. Version : 1.0
  7. Created : 18/10/2019
  8. Modified : 07/02/2020
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.Options;
  22. {$i QuickLib.inc}
  23. interface
  24. uses
  25. Classes,
  26. RTTI,
  27. System.TypInfo,
  28. System.SysUtils,
  29. System.Generics.Collections,
  30. Quick.RTTI.Utils,
  31. Quick.Commons,
  32. Quick.FileMonitor;
  33. type
  34. Required = class(TCustomAttribute);
  35. TValidationCustomAttribute = class(TCustomAttribute)
  36. protected
  37. fErrorMsg : string;
  38. public
  39. property ErrorMsg : string read fErrorMsg write fErrorMsg;
  40. end;
  41. Range = class(TValidationCustomAttribute)
  42. private
  43. fRangeMin : Double;
  44. fRangeMax : Double;
  45. public
  46. constructor Create(aMin, aMax : Integer; const aErrorMsg : string = ''); overload;
  47. constructor Create(aMin, aMax : Double; const aErrorMsg : string = ''); overload;
  48. property Min : Double read fRangeMin write fRangeMax;
  49. property Max : Double read fRangeMax write fRangeMax;
  50. end;
  51. StringLength = class(TValidationCustomAttribute)
  52. private
  53. fMaxLength : Integer;
  54. public
  55. constructor Create(aMaxLength : Integer; const aErrorMsg : string = '');
  56. property MaxLength : Integer read fMaxLength write fMaxLength;
  57. end;
  58. TOptions = class;
  59. TConfigureOptionsProc<T : TOptions> = reference to procedure(aOptions : T);
  60. IOptionsValidator = interface
  61. ['{C6A09F78-8E34-4689-B943-83620437B9EF}']
  62. procedure ValidateOptions;
  63. end;
  64. TOptions = class(TInterfacedObject,IOptionsValidator)
  65. private
  66. fName : string;
  67. fHideOptions : Boolean;
  68. procedure DoValidateOptions; virtual;
  69. public
  70. constructor Create; virtual;
  71. property Name : string read fName write fName;
  72. property HideOptions : Boolean read fHideOptions write fHideOptions;
  73. procedure DefaultValues; virtual;
  74. function ConfigureOptions<T : TOptions>(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
  75. procedure ValidateOptions;
  76. end;
  77. TOptionsValidator = class(TInterfacedObject,IOptionsValidator)
  78. private
  79. fOptions : TOptions;
  80. public
  81. constructor Create(aOptions : TOptions);
  82. procedure ValidateRequired(const aInstance : TObject; aProperty: TRttiProperty);
  83. procedure ValidateStringLength(const aInstance : TObject; aProperty: TRttiProperty; aValidation : StringLength);
  84. procedure ValidateRange(const aInstance : TObject; aProperty: TRttiProperty; aValidation : Range);
  85. procedure ValidateObject(aObj : TObject);
  86. procedure ValidateArray(aValue : TValue);
  87. procedure ValidateOptions;
  88. end;
  89. TOptions<T : TOptions> = record
  90. private
  91. fOptions : T;
  92. public
  93. constructor Create(aOptions : T);
  94. function ConfigureOptions(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
  95. end;
  96. IOptions<T : TOptions> = interface
  97. ['{2779F946-2692-4F74-88AD-F35F5137057A}']
  98. function GetSectionValue : T;
  99. property Value : T read GetSectionValue;
  100. end;
  101. TOptionsClass = class of TOptions;
  102. IOptionsContainer = interface
  103. ['{A593C8BB-53CF-4AA4-9641-BF974E45CBD1}']
  104. function AddSection(aOption : TOptionsClass; const aOptionsName : string = '') : TOptions;
  105. function GetOptions(aOptionClass : TOptionsClass): TOptions;
  106. function GetSection(aOptionsSection : TOptionsClass; var vOptions : TOptions) : Boolean; overload;
  107. procedure AddOption(aOption : TOptions);
  108. end;
  109. TSectionList = TObjectList<TOptions>;
  110. IOptionsSerializer = interface
  111. ['{7DECE203-4AAE-4C9D-86C8-B3D583DF7C8B}']
  112. function Load(const aFilename : string; aSections : TSectionList; aFailOnSectionNotExists : Boolean) : Boolean;
  113. procedure Save(const aFilename : string; aSections : TSectionList);
  114. function GetFileSectionNames(const aFilename : string; out oSections : TArray<string>) : Boolean;
  115. end;
  116. TOptionsSerializer = class(TInterfacedObject,IOptionsSerializer)
  117. public
  118. function Load(const aFilename : string; aSections : TSectionList; aFailOnSectionNotExists : Boolean) : Boolean; virtual; abstract;
  119. procedure Save(const aFilename : string; aSections : TSectionList); virtual; abstract;
  120. function GetFileSectionNames(const aFilename : string; out oSections : TArray<string>) : Boolean; virtual; abstract;
  121. end;
  122. TFileModifiedEvent = reference to procedure;
  123. TLoadConfigEvent = reference to procedure;
  124. TOptionValue<T : TOptions> = class(TInterfacedObject,IOptions<T>)
  125. private
  126. fValue : T;
  127. function GetSectionValue : T;
  128. public
  129. constructor Create(aValue : T);
  130. property Value : T read GetSectionValue;
  131. end;
  132. TOptionsContainer = class(TInterfacedObject,IOptionsContainer)
  133. private
  134. fFilename : string;
  135. fSerializer : IOptionsSerializer;
  136. fSections : TSectionList;
  137. fFileMonitor : TFileMonitor;
  138. fOnFileModified : TFileModifiedEvent;
  139. fLoaded : Boolean;
  140. fReloadIfFileChanged : Boolean;
  141. fOnConfigLoaded : TLoadConfigEvent;
  142. fOnConfigReloaded : TLoadConfigEvent;
  143. procedure CreateFileMonitor;
  144. procedure FileModifiedNotify(MonitorNotify : TMonitorNotify);
  145. procedure SetReloadIfFileChanged(const Value: Boolean);
  146. function GetOptions(aOptionClass : TOptionsClass): TOptions; overload;
  147. function GetOptions(aIndex : Integer) : TOptions; overload;
  148. function GetSection(aOptionsSection : TOptionsClass; var vOptions : TOptions) : Boolean; overload;
  149. public
  150. constructor Create(const aFilename : string; aOptionsSerializer : IOptionsSerializer; aReloadIfFileChanged : Boolean = False);
  151. destructor Destroy; override;
  152. property FileName : string read fFilename write fFilename;
  153. property ReloadIfFileChanged : Boolean read fReloadIfFileChanged write SetReloadIfFileChanged;
  154. property IsLoaded : Boolean read fLoaded;
  155. property OnFileModified : TFileModifiedEvent read fOnFileModified write fOnFileModified;
  156. property OnConfigLoaded : TLoadConfigEvent read fOnConfigLoaded write fOnConfigLoaded;
  157. property OnConfigReloaded : TLoadConfigEvent read fOnConfigReloaded write fOnConfigReloaded;
  158. property Items[aOptionClass : TOptionsClass] : TOptions read GetOptions; default;
  159. property Items[aIndex : Integer] : TOptions read GetOptions; default;
  160. function AddSection(aOption : TOptionsClass; const aSectionName : string = '') : TOptions; overload;
  161. function AddSection<T : TOptions>(const aSectionName : string = '') : TOptions<T>; overload;
  162. procedure AddOption(aOption : TOptions);
  163. function GetSectionInterface<T : TOptions> : IOptions<T>;
  164. function GetSection<T : TOptions>(const aSectionName : string = '') : T; overload;
  165. function GetFileSectionNames(out oSections : TArray<string>) : Boolean;
  166. function Count : Integer;
  167. procedure Load(aFailOnSectionNotExists : Boolean = False);
  168. procedure Save;
  169. end;
  170. IOptionsBuilder<T : TOptions> = interface
  171. ['{1A1DC9A9-7F2D-4CC4-A772-6C7DBAB34424}']
  172. function Options : T;
  173. end;
  174. TOptionsBuilder<T : TOptions> = class(TInterfacedObject,IOptionsBuilder<T>)
  175. protected
  176. fOptions : T;
  177. public
  178. constructor Create;
  179. function Options : T;
  180. end;
  181. EOptionConfigureError = class(Exception);
  182. EOptionLoadError = class(Exception);
  183. EOptionSaveError = class(Exception);
  184. EOptionValidationError = class(Exception);
  185. implementation
  186. { TOptionsContainer }
  187. constructor TOptionsContainer.Create(const aFilename : string; aOptionsSerializer : IOptionsSerializer; aReloadIfFileChanged : Boolean = False);
  188. begin
  189. fSerializer := aOptionsSerializer;
  190. fSections := TSectionList.Create(False);
  191. fFilename := aFilename;
  192. fLoaded := False;
  193. fReloadIfFileChanged := aReloadIfFileChanged;
  194. if aReloadIfFileChanged then CreateFileMonitor;
  195. end;
  196. procedure TOptionsContainer.CreateFileMonitor;
  197. begin
  198. fFileMonitor := TQuickFileMonitor.Create;
  199. fFileMonitor.FileName := fFilename;
  200. fFileMonitor.Interval := 2000;
  201. fFileMonitor.Notifies := [TMonitorNotify.mnFileModified];
  202. fFileMonitor.OnFileChange := FileModifiedNotify;
  203. fFileMonitor.Enabled := True;
  204. end;
  205. destructor TOptionsContainer.Destroy;
  206. var
  207. option : TOptions;
  208. begin
  209. if Assigned(fFileMonitor) then fFileMonitor.Free;
  210. fSerializer := nil;
  211. for option in fSections do
  212. begin
  213. if option.RefCount = 0 then option.Free;
  214. end;
  215. fSections.Free;
  216. inherited;
  217. end;
  218. procedure TOptionsContainer.FileModifiedNotify(MonitorNotify: TMonitorNotify);
  219. begin
  220. if MonitorNotify = TMonitorNotify.mnFileModified then
  221. begin
  222. if Assigned(fOnFileModified) then fOnFileModified;
  223. if fReloadIfFileChanged then
  224. begin
  225. Load(False);
  226. end;
  227. end;
  228. end;
  229. procedure TOptionsContainer.AddOption(aOption: TOptions);
  230. begin
  231. if aOption.Name.IsEmpty then aOption.Name := Copy(aOption.ClassName,2,aOption.ClassName.Length);
  232. fSections.Add(aOption);
  233. end;
  234. function TOptionsContainer.AddSection(aOption : TOptionsClass; const aSectionName : string = '') : TOptions;
  235. var
  236. option : TOptions;
  237. begin
  238. //if section already exists, returns it
  239. option := Self.GetOptions(aOption);
  240. if option <> nil then Exit(option);
  241. option := aOption.Create;
  242. if aSectionName.IsEmpty then option.Name := Copy(aOption.ClassName,2,aOption.ClassName.Length)
  243. else option.Name := aSectionName;
  244. fSections.Add(option);
  245. Result := option;
  246. end;
  247. function TOptionsContainer.AddSection<T>(const aSectionName: string): TOptions<T>;
  248. var
  249. option : TOptions;
  250. begin
  251. //if section already exists, returns it
  252. option := Self.GetSection<T>(aSectionName);
  253. if option <> nil then Exit(TOptions<T>(option));
  254. //new section
  255. option := TRTTI.CreateInstance<T>;
  256. if aSectionName.IsEmpty then option.Name := Copy(T.ClassName,2,T.ClassName.Length)
  257. else option.Name := aSectionName;
  258. fSections.Add(option);
  259. Result.Create(option);
  260. end;
  261. function TOptionsContainer.Count: Integer;
  262. begin
  263. Result := fSections.Count;
  264. end;
  265. function TOptionsContainer.GetFileSectionNames(out oSections : TArray<string>) : Boolean;
  266. begin
  267. Result := fSerializer.GetFileSectionNames(fFilename,oSections);
  268. end;
  269. function TOptionsContainer.GetOptions(aIndex: Integer): TOptions;
  270. begin
  271. Result := fSections[aIndex];
  272. end;
  273. function TOptionsContainer.GetSection(aOptionsSection: TOptionsClass; var vOptions: TOptions): Boolean;
  274. var
  275. option : TOptions;
  276. begin
  277. Result := False;
  278. for option in fSections do
  279. begin
  280. if option is TOptionsClass then
  281. begin
  282. vOptions := option as TOptionsClass;
  283. Exit;
  284. end;
  285. end;
  286. end;
  287. function TOptionsContainer.GetOptions(aOptionClass : TOptionsClass) : TOptions;
  288. var
  289. option : TOptions;
  290. begin
  291. Result := nil;
  292. for option in fSections do
  293. begin
  294. if option is aOptionClass then Result := option as TOptionsClass;
  295. end;
  296. end;
  297. function TOptionsContainer.GetSection<T>(const aSectionName : string = '') : T;
  298. var
  299. option : TOptions;
  300. begin
  301. Result := nil;
  302. for option in fSections do
  303. begin
  304. if option is T then
  305. begin
  306. if (aSectionName.IsEmpty) or (CompareText(option.Name,aSectionName) = 0) then
  307. begin
  308. Result := option as T;
  309. Exit;
  310. end;
  311. end;
  312. end;
  313. end;
  314. function TOptionsContainer.GetSectionInterface<T>: IOptions<T>;
  315. begin
  316. Result := TOptionValue<T>.Create(Self.GetSection<T>);
  317. end;
  318. procedure TOptionsContainer.Load(aFailOnSectionNotExists : Boolean = False);
  319. var
  320. option : TOptions;
  321. begin
  322. if FileExists(fFilename) then
  323. begin
  324. if not fSerializer.Load(fFilename,fSections,aFailOnSectionNotExists) then Save;
  325. if not fLoaded then
  326. begin
  327. fLoaded := True;
  328. if Assigned(fOnConfigLoaded) then fOnConfigLoaded;
  329. end
  330. else if Assigned(fOnConfigReloaded) then fOnConfigReloaded;
  331. end
  332. else
  333. begin
  334. //if not exists file get default values
  335. for option in fSections do option.DefaultValues;
  336. //creates default file
  337. Save;
  338. end;
  339. end;
  340. procedure TOptionsContainer.Save;
  341. var
  342. laststate : Boolean;
  343. begin
  344. //disable filemonitor to avoid detect manual save as a external file change
  345. if fReloadIfFileChanged then
  346. begin
  347. laststate := fFileMonitor.Enabled;
  348. fFileMonitor.Enabled := False;
  349. try
  350. //save config file
  351. fSerializer.Save(fFilename,fSections);
  352. finally
  353. //set last state
  354. fFileMonitor.Enabled := laststate;
  355. end;
  356. end
  357. else fSerializer.Save(fFilename,fSections);
  358. end;
  359. procedure TOptionsContainer.SetReloadIfFileChanged(const Value: Boolean);
  360. begin
  361. if Value = fReloadIfFileChanged then Exit;
  362. fReloadIfFileChanged := Value;
  363. if Assigned(fFileMonitor) then fFileMonitor.Free;
  364. if fReloadIfFileChanged then CreateFileMonitor;
  365. end;
  366. { TOptions }
  367. function TOptions.ConfigureOptions<T>(aOptionsFunc: TConfigureOptionsProc<T>): IOptionsValidator;
  368. var
  369. value : TValue;
  370. begin
  371. Result := TOptionsValidator.Create(Self);
  372. if Assigned(aOptionsFunc) then
  373. begin
  374. value := Self;
  375. aOptionsFunc(value.AsType<T>);
  376. end;
  377. end;
  378. constructor TOptions.Create;
  379. begin
  380. fName := '';
  381. fHideOptions := False;
  382. end;
  383. procedure TOptions.DefaultValues;
  384. begin
  385. //nothing
  386. end;
  387. procedure TOptions.DoValidateOptions;
  388. var
  389. ivalidator : IOptionsValidator;
  390. begin
  391. ivalidator := TOptionsValidator.Create(Self);
  392. ivalidator.ValidateOptions;
  393. end;
  394. procedure TOptions.ValidateOptions;
  395. begin
  396. try
  397. DoValidateOptions;
  398. except
  399. on E : Exception do
  400. begin
  401. raise EOptionConfigureError.CreateFmt('Validation Options Error : %s',[e.Message]);
  402. end;
  403. end;
  404. end;
  405. { TOptionsValidator }
  406. procedure TOptionsValidator.ValidateObject(aObj : TObject);
  407. var
  408. ctx : TRttiContext;
  409. rtype : TRttiType;
  410. rprop : TRttiProperty;
  411. attrib : TCustomAttribute;
  412. rvalue : TValue;
  413. begin
  414. ctx := TRttiContext.Create;
  415. try
  416. rtype := ctx.GetType(aObj.ClassInfo);
  417. for rprop in rtype.GetProperties do
  418. begin
  419. //check only published properties
  420. if rprop.Visibility = TMemberVisibility.mvPublished then
  421. begin
  422. //check validation option attributes
  423. for attrib in rprop.GetAttributes do
  424. begin
  425. if attrib is Required then ValidateRequired(aObj,rprop)
  426. else if attrib is StringLength then ValidateStringLength(aObj,rprop,StringLength(attrib))
  427. else if attrib is Range then ValidateRange(aObj,rprop,Range(attrib));
  428. end;
  429. rvalue := rprop.GetValue(aObj);
  430. if not rvalue.IsEmpty then
  431. begin
  432. case rvalue.Kind of
  433. tkClass : ValidateObject(rvalue.AsObject);
  434. tkDynArray : ValidateArray(rvalue);
  435. end;
  436. end;
  437. end;
  438. end;
  439. finally
  440. ctx.Free;
  441. end;
  442. end;
  443. constructor TOptionsValidator.Create(aOptions: TOptions);
  444. begin
  445. fOptions := aOptions;
  446. end;
  447. procedure TOptionsValidator.ValidateOptions;
  448. begin
  449. ValidateObject(fOptions);
  450. end;
  451. procedure TOptionsValidator.ValidateArray(aValue : TValue);
  452. type
  453. PPByte = ^PByte;
  454. var
  455. ctx : TRttiContext;
  456. rDynArray : TRttiDynamicArrayType;
  457. itvalue : TValue;
  458. i : Integer;
  459. begin
  460. ctx := TRttiContext.Create;
  461. try
  462. rDynArray := ctx.GetType(aValue.TypeInfo) as TRTTIDynamicArrayType;
  463. for i := 0 to aValue.GetArrayLength - 1 do
  464. begin
  465. TValue.Make(PPByte(aValue.GetReferenceToRawData)^ + rDynArray.ElementType.TypeSize * i, rDynArray.ElementType.Handle,itvalue);
  466. if not itvalue.IsEmpty then
  467. begin
  468. case itvalue.Kind of
  469. tkClass : ValidateObject(itvalue.AsObject);
  470. tkDynArray : ValidateArray(itvalue);
  471. end;
  472. end;
  473. end;
  474. finally
  475. ctx.Free;
  476. end;
  477. end;
  478. procedure TOptionsValidator.ValidateRange(const aInstance : TObject; aProperty: TRttiProperty; aValidation : Range);
  479. var
  480. value : TValue;
  481. msg : string;
  482. begin
  483. value := aProperty.GetValue(aInstance);
  484. if not value.IsEmpty then
  485. begin
  486. if value.Kind = tkFloat then
  487. begin
  488. if (value.AsExtended < aValidation.Min) or (value.AsExtended > aValidation.Max) then
  489. begin
  490. if aValidation.ErrorMsg.IsEmpty then msg := Format('Option %s "%s.%s" exceeds predefined range (%2f - %2f)',[fOptions.Name,aInstance.ClassName,aProperty.Name,aValidation.Min,aValidation.Max])
  491. else msg := aValidation.ErrorMsg;
  492. raise EOptionValidationError.Create(msg);
  493. end;
  494. end
  495. else if value.Kind in [tkInteger,tkInt64] then
  496. begin
  497. if (value.AsInt64 < aValidation.Min) or (value.AsInt64 > aValidation.Max) then
  498. begin
  499. if aValidation.ErrorMsg.IsEmpty then msg := Format('Option %s "%s.%s" exceeds predefined range (%d - %d)',[fOptions.Name,aInstance.ClassName,aProperty.Name,Round(aValidation.Min),Round(aValidation.Max)])
  500. else msg := aValidation.ErrorMsg;
  501. raise EOptionValidationError.Create(msg);
  502. end;
  503. end;
  504. end;
  505. end;
  506. procedure TOptionsValidator.ValidateRequired(const aInstance : TObject; aProperty: TRttiProperty);
  507. begin
  508. if aProperty.GetValue(aInstance).IsEmpty then raise EOptionValidationError.CreateFmt('Option %s "%s.%s" is required',[fOptions.Name,aInstance.ClassName,aProperty.Name]);
  509. end;
  510. procedure TOptionsValidator.ValidateStringLength(const aInstance : TObject; aProperty: TRttiProperty; aValidation : StringLength);
  511. var
  512. value : TValue;
  513. msg : string;
  514. begin
  515. value := aProperty.GetValue(aInstance);
  516. if (not value.IsEmpty) and (value.AsString.Length > aValidation.MaxLength) then
  517. begin
  518. if aValidation.ErrorMsg.IsEmpty then msg := Format('Option %s "%s.%s" exceeds max length (%d)',[fOptions.Name,aInstance.ClassName,aProperty.Name,aValidation.MaxLength])
  519. else msg := aValidation.ErrorMsg;
  520. raise EOptionValidationError.Create(msg);
  521. end;
  522. end;
  523. { Range }
  524. constructor Range.Create(aMin, aMax: Integer; const aErrorMsg : string = '');
  525. begin
  526. fRangeMin := aMin;
  527. fRangeMax := aMax;
  528. fErrorMsg := aErrorMsg;
  529. end;
  530. constructor Range.Create(aMin, aMax: Double; const aErrorMsg: string);
  531. begin
  532. fRangeMin := aMin;
  533. fRangeMax := aMax;
  534. fErrorMsg := aErrorMsg;
  535. end;
  536. { StringLength }
  537. constructor StringLength.Create(aMaxLength: Integer; const aErrorMsg : string = '');
  538. begin
  539. fMaxLength := aMaxLength;
  540. fErrorMsg := aErrorMsg;
  541. end;
  542. { TOptionValue<T> }
  543. constructor TOptionValue<T>.Create(aValue: T);
  544. begin
  545. fValue := aValue;
  546. end;
  547. function TOptionValue<T>.GetSectionValue: T;
  548. begin
  549. Result := fValue;
  550. end;
  551. { TOptions<T> }
  552. function TOptions<T>.ConfigureOptions(aOptionsFunc: TConfigureOptionsProc<T>): IOptionsValidator;
  553. begin
  554. if Assigned(aOptionsFunc) then Result := fOptions.ConfigureOptions<T>(aOptionsFunc)
  555. else Result := TOptionsValidator.Create(fOptions);
  556. end;
  557. constructor TOptions<T>.Create(aOptions: T);
  558. begin
  559. fOptions := aOptions;
  560. end;
  561. { TOptionsBuilder<T> }
  562. constructor TOptionsBuilder<T>.Create;
  563. begin
  564. fOptions := (PTypeInfo(TypeInfo(T)).TypeData.ClassType.Create) as T;
  565. end;
  566. function TOptionsBuilder<T>.Options: T;
  567. begin
  568. Result := fOptions;
  569. end;
  570. end.