Quick.RTTI.Utils.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. { ***************************************************************************
  2. Copyright (c) 2016-2020 Kike Pérez
  3. Unit : Quick.RTTI.Utils
  4. Description : Files functions
  5. Author : Kike Pérez
  6. Version : 1.4
  7. Created : 09/03/2018
  8. Modified : 12/03/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.RTTI.Utils;
  22. {$i QuickLib.inc}
  23. interface
  24. uses
  25. SysUtils,
  26. Quick.Commons,
  27. TypInfo,
  28. Rtti;
  29. type
  30. TRTTI = class
  31. private class var
  32. fCtx : TRttiContext;
  33. public
  34. {$IFNDEF FPC}
  35. class constructor Create;
  36. class destructor Destroy;
  37. class function GetField(aInstance : TObject; const aFieldName : string) : TRttiField; overload;
  38. class function GetField(aTypeInfo : Pointer; const aFieldName : string) : TRttiField; overload;
  39. class function FieldExists(aTypeInfo : Pointer; const aFieldName : string) : Boolean;
  40. class function GetFieldValue(aInstance : TObject; const aFieldName : string) : TValue; overload;
  41. class function GetFieldValue(aTypeInfo : Pointer; const aFieldName: string) : TValue; overload;
  42. {$ENDIF}
  43. class function GetType(aTypeInfo : Pointer) : TRttiType;
  44. class function GetProperty(aInstance : TObject; const aPropertyName : string) : TRttiProperty; overload;
  45. class function GetProperty(aTypeInfo : Pointer; const aPropertyName : string) : TRttiProperty; overload;
  46. class function GetPropertyPath(aInstance : TObject; const aPropertyPath : string) : TRttiProperty;
  47. {$IFNDEF FPC}
  48. class function GetMemberPath(aInstance: TObject; const aPropertyPath: string): TRttiMember;
  49. {$ENDIF}
  50. class function PathExists(aInstance: TObject; const aPropertyPath: string) : Boolean;
  51. class function GetPathValue(aInstance : TObject; const aPropertyPath : string) : TValue;
  52. class procedure SetPathValue(aInstance: TObject; const aPropertyPath: string; aValue : TValue);
  53. class procedure SetPropertyValue(aInstance : TObject; const aPropertyName : string; aValue : TValue);
  54. class function PropertyExists(aTypeInfo : Pointer; const aPropertyName : string) : Boolean;
  55. class function GetPropertyValue(aInstance : TObject; const aPropertyName : string) : TValue; overload;
  56. class function GetPropertyValue(aTypeInfo : Pointer; const aPropertyName : string) : TValue; overload;
  57. class function GetPropertyValueEx(aInstance: TObject; const aPropertyName: string): TValue;
  58. {$IFNDEF FPC}
  59. class function FindClass(const aClassName: string): TClass;
  60. class function CreateInstance<T>: T;
  61. {$ENDIF}
  62. end;
  63. ERTTIError = class(Exception);
  64. implementation
  65. { TRTTIUtils }
  66. {$IFNDEF FPC}
  67. class constructor TRTTI.Create;
  68. begin
  69. fCtx := TRttiContext.Create;
  70. end;
  71. class function TRTTI.CreateInstance<T>: T;
  72. var
  73. value: TValue;
  74. rtype: TRttiType;
  75. rmethod: TRttiMethod;
  76. rinstype: TRttiInstanceType;
  77. begin
  78. rtype := fCtx.GetType(TypeInfo(T));
  79. for rmethod in rtype.GetMethods do
  80. begin
  81. if (rmethod.IsConstructor) and (Length(rmethod.GetParameters) = 0) then
  82. begin
  83. rinstype := rtype.AsInstance;
  84. value := rmethod.Invoke(rinstype.MetaclassType,[]);
  85. Result := value.AsType<T>;
  86. Exit;
  87. end;
  88. end;
  89. end;
  90. class destructor TRTTI.Destroy;
  91. begin
  92. fCtx.Free;
  93. end;
  94. class function TRTTI.FieldExists(aTypeInfo: Pointer; const aFieldName: string): Boolean;
  95. var
  96. rtype : TRttiType;
  97. begin
  98. rtype := fCtx.GetType(aTypeInfo);
  99. Result := rtype.GetField(aFieldName) <> nil;
  100. end;
  101. class function TRTTI.GetField(aInstance: TObject; const aFieldName: string): TRttiField;
  102. var
  103. rtype : TRttiType;
  104. begin
  105. Result := nil;
  106. rtype := fCtx.GetType(aInstance.ClassInfo);
  107. if rtype <> nil then
  108. begin
  109. Result := rtype.GetField(aFieldName);
  110. end;
  111. end;
  112. class function TRTTI.GetField(aTypeInfo: Pointer; const aFieldName: string): TRttiField;
  113. var
  114. rtype : TRttiType;
  115. begin
  116. Result := nil;
  117. rtype := fCtx.GetType(aTypeInfo);
  118. if rtype <> nil then
  119. begin
  120. Result := rtype.GetField(aFieldName);
  121. end;
  122. end;
  123. class function TRTTI.GetFieldValue(aInstance : TObject; const aFieldName: string): TValue;
  124. var
  125. rfield: TRttiField;
  126. begin
  127. rfield := GetField(aInstance,aFieldName);
  128. if rfield <> nil then Result := rfield.GetValue(aInstance);
  129. end;
  130. class function TRTTI.GetFieldValue(aTypeInfo : Pointer; const aFieldName: string): TValue;
  131. var
  132. rfield: TRttiField;
  133. begin
  134. rfield := GetField(aTypeInfo,aFieldName);
  135. if rfield <> nil then rfield.GetValue(aTypeInfo);
  136. end;
  137. {$ENDIF}
  138. class function TRTTI.GetProperty(aInstance: TObject; const aPropertyName: string): TRttiProperty;
  139. var
  140. rtype : TRttiType;
  141. begin
  142. Result := nil;
  143. rtype := fCtx.GetType(aInstance.ClassInfo);
  144. if rtype <> nil then Result := rtype.GetProperty(aPropertyName);
  145. end;
  146. class function TRTTI.GetProperty(aTypeInfo: Pointer; const aPropertyName: string): TRttiProperty;
  147. var
  148. rtype : TRttiType;
  149. begin
  150. Result := nil;
  151. rtype := fCtx.GetType(aTypeInfo);
  152. if rtype <> nil then Result := rtype.GetProperty(aPropertyName);
  153. end;
  154. class function TRTTI.GetPropertyPath(aInstance: TObject; const aPropertyPath: string): TRttiProperty;
  155. var
  156. prop : TRttiProperty;
  157. proppath : string;
  158. propname : string;
  159. i : Integer;
  160. value : TValue;
  161. rtype : TRttiType;
  162. {$IFNDEF FPC}
  163. rfield : TRttiField;
  164. {$ENDIF}
  165. lastsegment : Boolean;
  166. begin
  167. Result := nil;
  168. proppath := aPropertyPath;
  169. lastsegment := False;
  170. rtype := fCtx.GetType(aInstance.ClassType);
  171. repeat
  172. i := proppath.IndexOf('.');
  173. if i > -1 then
  174. begin
  175. propname := Copy(proppath,1,i);
  176. Delete(proppath,1,i+1);
  177. end
  178. else
  179. begin
  180. propname := proppath;
  181. lastsegment := True;
  182. end;
  183. if rtype.TypeKind = TTypeKind.tkRecord then
  184. begin
  185. {$IFNDEF FPC}
  186. rfield := rtype.GetField(propname);
  187. if rfield <> nil then value := rfield.GetValue(aInstance);
  188. {$ELSE}
  189. raise ERTTIError.Create('FPC not supports record fields in RTTI');
  190. {$ENDIF}
  191. end
  192. else
  193. begin
  194. prop := rtype.GetProperty(propname);
  195. if prop = nil then Exit;
  196. if lastsegment then Exit(prop)
  197. else value := prop.GetValue(aInstance);
  198. end;
  199. if not lastsegment then
  200. begin
  201. if value.Kind = TTypeKind.tkClass then rType := fCtx.GetType(value.AsObject.ClassType)
  202. else if value.Kind = TTypeKind.tkRecord then rtype := fCtx.GetType(value.TypeInfo);
  203. end;
  204. until lastsegment;
  205. Result := nil;
  206. end;
  207. {$IFNDEF FPC}
  208. class function TRTTI.GetMemberPath(aInstance: TObject; const aPropertyPath: string): TRttiMember;
  209. var
  210. prop : TRttiProperty;
  211. proppath : string;
  212. propname : string;
  213. i : Integer;
  214. value : TValue;
  215. rtype : TRttiType;
  216. {$IFNDEF FPC}
  217. rfield : TRttiField;
  218. {$ENDIF}
  219. lastsegment : Boolean;
  220. begin
  221. Result := nil;
  222. proppath := aPropertyPath;
  223. lastsegment := False;
  224. rtype := fCtx.GetType(aInstance.ClassType);
  225. repeat
  226. i := proppath.IndexOf('.');
  227. if i > -1 then
  228. begin
  229. propname := Copy(proppath,1,i);
  230. Delete(proppath,1,i+1);
  231. end
  232. else
  233. begin
  234. propname := proppath;
  235. lastsegment := True;
  236. end;
  237. if rtype.TypeKind = TTypeKind.tkRecord then
  238. begin
  239. {$IFNDEF FPC}
  240. rfield := rtype.GetField(propname);
  241. if rfield <> nil then
  242. begin
  243. if lastsegment then Exit(rfield)
  244. else value := rfield.GetValue(value.GetReferenceToRawData);
  245. end;
  246. {$ELSE}
  247. raise ERTTIError.Create('FPC not supports record fields in RTTI');
  248. {$ENDIF}
  249. end
  250. else
  251. begin
  252. prop := rtype.GetProperty(propname);
  253. if prop = nil then Exit;
  254. if lastsegment then Exit(prop)
  255. else value := prop.GetValue(aInstance);
  256. end;
  257. if not lastsegment then
  258. begin
  259. if value.Kind = TTypeKind.tkClass then rType := fCtx.GetType(value.AsObject.ClassType)
  260. else if value.Kind = TTypeKind.tkRecord then rtype := fCtx.GetType(value.TypeInfo);
  261. end;
  262. until lastsegment;
  263. end;
  264. {$ENDIF}
  265. class function TRTTI.PathExists(aInstance: TObject; const aPropertyPath: string) : Boolean;
  266. var
  267. proppath : string;
  268. propname : string;
  269. i : Integer;
  270. value : TValue;
  271. rtype : TRttiType;
  272. rprop : TRttiProperty;
  273. {$IFNDEF FPC}
  274. rfield : TRttiField;
  275. {$ENDIF}
  276. lastsegment : Boolean;
  277. begin
  278. if not Assigned(aInstance) then Exit(False);
  279. lastsegment := False;
  280. proppath := aPropertyPath;
  281. rtype := fCtx.GetType(aInstance.ClassType);
  282. repeat
  283. Result := False;
  284. i := proppath.IndexOf('.');
  285. if i > -1 then
  286. begin
  287. propname := Copy(proppath,1,i);
  288. Delete(proppath,1,i+1);
  289. end
  290. else
  291. begin
  292. propname := proppath;
  293. lastsegment := True;
  294. end;
  295. if rtype.TypeKind = TTypeKind.tkRecord then
  296. begin
  297. {$IFNDEF FPC}
  298. rfield := rtype.GetField(propname);
  299. if rfield = nil then Exit
  300. else
  301. begin
  302. value := rfield.GetValue(value.GetReferenceToRawData);
  303. Result := True;
  304. end;
  305. {$ELSE}
  306. raise ERTTIError.Create('FPC not supports record fields in RTTI');
  307. {$ENDIF}
  308. end
  309. else
  310. begin
  311. rprop := rtype.GetProperty(propname);
  312. if rprop = nil then Exit
  313. else
  314. begin
  315. value := rprop.GetValue(aInstance);
  316. Result := True;
  317. end;
  318. end;
  319. if not lastsegment then
  320. begin
  321. if value.Kind = TTypeKind.tkClass then rType := fCtx.GetType(value.AsObject.ClassType)
  322. else if value.Kind = TTypeKind.tkRecord then rtype := fCtx.GetType(value.TypeInfo);
  323. end;
  324. until lastsegment;
  325. end;
  326. class function TRTTI.GetPathValue(aInstance: TObject; const aPropertyPath: string): TValue;
  327. var
  328. proppath : string;
  329. propname : string;
  330. i : Integer;
  331. value : TValue;
  332. rtype : TRttiType;
  333. rprop : TRttiProperty;
  334. {$IFNDEF FPC}
  335. rfield : TRttiField;
  336. {$ENDIF}
  337. lastsegment : Boolean;
  338. begin
  339. Result := nil;
  340. if not Assigned(aInstance) then Exit;
  341. lastsegment := False;
  342. proppath := aPropertyPath;
  343. rtype := fCtx.GetType(aInstance.ClassType);
  344. {$IFDEF FPC}
  345. value := aInstance;
  346. {$ENDIF}
  347. repeat
  348. i := proppath.IndexOf('.');
  349. if i > -1 then
  350. begin
  351. propname := Copy(proppath,1,i);
  352. Delete(proppath,1,i+1);
  353. end
  354. else
  355. begin
  356. propname := proppath;
  357. lastsegment := True;
  358. end;
  359. if rtype.TypeKind = TTypeKind.tkRecord then
  360. begin
  361. {$IFNDEF FPC}
  362. rfield := rtype.GetField(propname);
  363. if rfield = nil then raise ERTTIError.CreateFmt('Field "%s" not found in record',[propname])
  364. else value := rfield.GetValue(value.GetReferenceToRawData);
  365. {$ELSE}
  366. raise ERTTIError.Create('FPC not supports record fields in RTTI');
  367. {$ENDIF}
  368. end
  369. else
  370. begin
  371. rprop := rtype.GetProperty(propname);
  372. if rprop = nil then raise ERTTIError.CreateFmt('Property "%s" not found in object',[propname])
  373. {$IFNDEF FPC}
  374. else value := rprop.GetValue(aInstance);
  375. {$ELSE}
  376. else
  377. begin
  378. if rprop.PropertyType.IsInstance then value := GetObjectProp(value.AsObject,propname)
  379. else value := rprop.GetValue(value.AsObject);
  380. end;
  381. {$ENDIF}
  382. end;
  383. if not lastsegment then
  384. begin
  385. if value.Kind = TTypeKind.tkClass then rType := fCtx.GetType(value.AsObject.ClassType)
  386. else if value.Kind = TTypeKind.tkRecord then rtype := fCtx.GetType(value.TypeInfo);
  387. end;
  388. until lastsegment;
  389. Result := value;
  390. end;
  391. class procedure TRTTI.SetPathValue(aInstance: TObject; const aPropertyPath: string; aValue : TValue);
  392. var
  393. proppath : string;
  394. propname : string;
  395. i : Integer;
  396. value : TValue;
  397. rtype : TRttiType;
  398. rprop : TRttiProperty;
  399. {$IFNDEF FPC}
  400. rfield : TRttiField;
  401. {$ENDIF}
  402. lastsegment : Boolean;
  403. begin
  404. if not Assigned(aInstance) then Exit;
  405. lastsegment := False;
  406. proppath := aPropertyPath;
  407. rtype := fCtx.GetType(aInstance.ClassType);
  408. repeat
  409. i := proppath.IndexOf('.');
  410. if i > -1 then
  411. begin
  412. propname := Copy(proppath,1,i);
  413. Delete(proppath,1,i+1);
  414. end
  415. else
  416. begin
  417. propname := proppath;
  418. lastsegment := True;
  419. end;
  420. if rtype.TypeKind = TTypeKind.tkRecord then
  421. begin
  422. {$IFNDEF FPC}
  423. rfield := rtype.GetField(propname);
  424. if rfield = nil then raise ERTTIError.CreateFmt('Field "%s" not found in record',[propname])
  425. else
  426. begin
  427. if lastsegment then rfield.SetValue(value.GetReferenceToRawData,aValue)
  428. else value := rfield.GetValue(value.GetReferenceToRawData);
  429. end;
  430. {$ELSE}
  431. raise ERTTIError.Create('FPC not supports record fields in RTTI');
  432. {$ENDIF}
  433. end
  434. else
  435. begin
  436. rprop := rtype.GetProperty(propname);
  437. if rprop = nil then raise ERTTIError.CreateFmt('Property "%s" not found in object',[propname])
  438. else
  439. begin
  440. if lastsegment then rprop.SetValue(aInstance,aValue)
  441. else value := rprop.GetValue(aInstance);
  442. end;
  443. end;
  444. if not lastsegment then
  445. begin
  446. if value.Kind = TTypeKind.tkClass then rType := fCtx.GetType(value.AsObject.ClassType)
  447. else if value.Kind = TTypeKind.tkRecord then rtype := fCtx.GetType(value.TypeInfo);
  448. end;
  449. until lastsegment;
  450. end;
  451. class function TRTTI.GetPropertyValue(aInstance: TObject; const aPropertyName: string): TValue;
  452. var
  453. rprop : TRttiProperty;
  454. begin
  455. rprop := GetProperty(aInstance,aPropertyName);
  456. if rprop <> nil then
  457. begin
  458. {$IFNDEF FPC}
  459. Result := rprop.GetValue(aInstance);
  460. {$ELSE}
  461. if rprop.PropertyType.IsInstance then Result := GetObjectProp(aInstance,aPropertyName)
  462. else Result := rprop.GetValue(aInstance);
  463. {$ENDIF}
  464. end;
  465. end;
  466. class function TRTTI.GetPropertyValue(aTypeInfo: Pointer; const aPropertyName: string): TValue;
  467. var
  468. rprop : TRttiProperty;
  469. begin
  470. rprop := GetProperty(aTypeInfo,aPropertyName);
  471. if rprop <> nil then
  472. begin
  473. {$IFNDEF FPC}
  474. Result := rprop.GetValue(aTypeInfo);
  475. {$ELSE}
  476. if rprop.PropertyType.IsInstance then Result := GetObjectProp(aTypeInfo,aPropertyName)
  477. else Result := rprop.GetValue(aTypeInfo);
  478. {$ENDIF}
  479. end;
  480. end;
  481. class function TRTTI.GetPropertyValueEx(aInstance: TObject; const aPropertyName: string): TValue;
  482. var
  483. pinfo : PPropInfo;
  484. begin
  485. Result := nil;
  486. pinfo := GetPropInfo(aInstance,aPropertyName);
  487. if pinfo = nil then
  488. begin
  489. //if not found can be a public property
  490. Result := GetPropertyValue(aInstance,aPropertyName);
  491. Exit;
  492. end;
  493. case pinfo.PropType^.Kind of
  494. tkInteger : Result := GetOrdProp(aInstance,pinfo);
  495. tkInt64 : Result := GetInt64Prop(aInstance,aPropertyName);
  496. tkFloat : Result := GetFloatProp(aInstance,aPropertyName);
  497. tkChar : Result := Char(GetOrdProp(aInstance,aPropertyName));
  498. {$IFDEF FPC}
  499. tkWString : Result := GetWideStrProp(aInstance,aPropertyName);
  500. tkSString,
  501. tkAString,
  502. {$ELSE}
  503. tkUString,
  504. tkWString,
  505. {$ENDIF}
  506. tkLString : Result := GetStrProp(aInstance,pinfo);
  507. {$IFDEF FPC}
  508. tkEnumeration :Result := GetOrdProp(aInstance,aPropertyName);
  509. {$ELSE}
  510. tkEnumeration : Result := GetOrdProp(aInstance,aPropertyName);
  511. {$ENDIF}
  512. tkSet : Result := GetSetProp(aInstance,pinfo,True);
  513. {$IFNDEF FPC}
  514. tkClass :
  515. {$ELSE}
  516. tkBool : Result := Boolean(GetOrdProp(aInstance,pinfo));
  517. tkObject :
  518. {$ENDIF} Result := GetObjectProp(aInstance,pinfo);
  519. tkDynArray : Result := GetDynArrayProp(aInstance,pinfo);
  520. end;
  521. end;
  522. class function TRTTI.GetType(aTypeInfo: Pointer): TRttiType;
  523. begin
  524. Result := fCtx.GetType(aTypeInfo);
  525. end;
  526. class function TRTTI.PropertyExists(aTypeInfo: Pointer; const aPropertyName: string) : Boolean;
  527. var
  528. rtype : TRttiType;
  529. begin
  530. Result := False;
  531. rtype := fCtx.GetType(aTypeInfo);
  532. if rtype <> nil then Result := rtype.GetProperty(aPropertyName) <> nil;
  533. end;
  534. class procedure TRTTI.SetPropertyValue(aInstance: TObject; const aPropertyName: string; aValue: TValue);
  535. var
  536. rprop : TRttiProperty;
  537. begin
  538. rprop := GetProperty(aInstance,aPropertyName);
  539. if rprop <> nil then rprop.SetValue(aInstance,aValue);
  540. end;
  541. {$IFNDEF FPC}
  542. class function TRTTI.FindClass(const aClassName: string): TClass;
  543. var
  544. rType : TRttiType;
  545. rList : TArray<TRttiType>;
  546. begin
  547. Result := nil;
  548. rList := fCtx.GetTypes;
  549. for rType in rList do
  550. begin
  551. if (rType.IsInstance) and (aClassName.EndsWith(rType.Name)) then
  552. begin
  553. Result := rType.AsInstance.MetaClassType;
  554. Break;
  555. end;
  556. end;
  557. end;
  558. {$ENDIF}
  559. end.