Quick.RTTI.Utils.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. { ***************************************************************************
  2. Copyright (c) 2016-2019 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 : 21/05/2019
  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. {$IFDEF FPC}
  28. TypInfo,
  29. {$ENDIF}
  30. Rtti;
  31. type
  32. TRTTI = class
  33. private class var
  34. fCtx : TRttiContext;
  35. public
  36. class function GetType(aTypeInfo : Pointer) : TRttiType;
  37. {$IFNDEF FPC}
  38. class function GetField(aInstance : TObject; const aFieldName : string) : TRttiField; overload;
  39. class function GetField(aTypeInfo : Pointer; const aFieldName : string) : TRttiField; overload;
  40. class function FieldExists(aTypeInfo : Pointer; const aFieldName : string) : Boolean;
  41. class function GetFieldValue(aInstance : TObject; const aFieldName : string) : TValue; overload;
  42. class function GetFieldValue(aTypeInfo : Pointer; const aFieldName: string) : TValue; overload;
  43. {$ENDIF}
  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. class function PathExists(aInstance: TObject; const aPropertyPath: string) : Boolean;
  48. class function GetPathValue(aInstance : TObject; const aPropertyPath : string) : TValue;
  49. class procedure SetPathValue(aInstance: TObject; const aPropertyPath: string; aValue : TValue);
  50. class procedure SetPropertyValue(aInstance : TObject; const aPropertyName : string; aValue : TValue);
  51. class function PropertyExists(aTypeInfo : Pointer; const aPropertyName : string) : Boolean;
  52. class function GetPropertyValue(aInstance : TObject; const aPropertyName : string) : TValue; overload;
  53. class function GetPropertyValue(aTypeInfo : Pointer; const aPropertyName : string) : TValue; overload;
  54. {$IFNDEF FPC}
  55. class function FindClass(const aClassName: string): TClass;
  56. {$ENDIF}
  57. end;
  58. ERTTIError = class(Exception);
  59. implementation
  60. { TRTTIUtils }
  61. {$IFNDEF FPC}
  62. class function TRTTI.FieldExists(aTypeInfo: Pointer; const aFieldName: string): Boolean;
  63. var
  64. rtype : TRttiType;
  65. begin
  66. rtype := fCtx.GetType(aTypeInfo);
  67. Result := rtype.GetField(aFieldName) <> nil;
  68. end;
  69. class function TRTTI.GetField(aInstance: TObject; const aFieldName: string): TRttiField;
  70. var
  71. rtype : TRttiType;
  72. begin
  73. rtype := fCtx.GetType(aInstance.ClassInfo);
  74. if rtype <> nil then
  75. begin
  76. Result := rtype.GetField(aFieldName);
  77. end;
  78. end;
  79. class function TRTTI.GetField(aTypeInfo: Pointer; const aFieldName: string): TRttiField;
  80. var
  81. rtype : TRttiType;
  82. begin
  83. rtype := fCtx.GetType(aTypeInfo);
  84. if rtype <> nil then
  85. begin
  86. Result := rtype.GetField(aFieldName);
  87. end;
  88. end;
  89. class function TRTTI.GetFieldValue(aInstance : TObject; const aFieldName: string): TValue;
  90. var
  91. rfield: TRttiField;
  92. begin
  93. rfield := GetField(aInstance,aFieldName);
  94. if rfield <> nil then Result := rfield.GetValue(aInstance);
  95. end;
  96. class function TRTTI.GetFieldValue(aTypeInfo : Pointer; const aFieldName: string): TValue;
  97. var
  98. rfield: TRttiField;
  99. begin
  100. rfield := GetField(aTypeInfo,aFieldName);
  101. if rfield <> nil then rfield.GetValue(aTypeInfo);
  102. end;
  103. {$ENDIF}
  104. class function TRTTI.GetProperty(aInstance: TObject; const aPropertyName: string): TRttiProperty;
  105. var
  106. rtype : TRttiType;
  107. begin
  108. rtype := fCtx.GetType(aInstance.ClassInfo);
  109. if rtype <> nil then Result := rtype.GetProperty(aPropertyName);
  110. end;
  111. class function TRTTI.GetProperty(aTypeInfo: Pointer; const aPropertyName: string): TRttiProperty;
  112. var
  113. rtype : TRttiType;
  114. begin
  115. rtype := fCtx.GetType(aTypeInfo);
  116. if rtype <> nil then Result := rtype.GetProperty(aPropertyName);
  117. end;
  118. class function TRTTI.GetPropertyPath(aInstance: TObject; const aPropertyPath: string): TRttiProperty;
  119. var
  120. prop : TRttiProperty;
  121. proppath : string;
  122. propname : string;
  123. i : Integer;
  124. value : TValue;
  125. rtype : TRttiType;
  126. {$IFNDEF FPC}
  127. rfield : TRttiField;
  128. {$ENDIF}
  129. begin
  130. Result := nil;
  131. proppath := aPropertyPath;
  132. rtype := fCtx.GetType(aInstance.ClassType);
  133. repeat
  134. i := proppath.IndexOf('.');
  135. if i > -1 then
  136. begin
  137. propname := Copy(proppath,1,i);
  138. Delete(proppath,1,i+1);
  139. end
  140. else propname := proppath;
  141. if rtype.TypeKind = TTypeKind.tkRecord then
  142. begin
  143. {$IFNDEF FPC}
  144. rfield := rtype.GetField(propname);
  145. if rfield <> nil then value := rfield.GetValue(aInstance);
  146. {$ELSE}
  147. raise ERTTIError.Create('FPC not supports record fields in RTTI');
  148. {$ENDIF}
  149. end
  150. else
  151. begin
  152. prop := rtype.GetProperty(propname);
  153. if prop = nil then Exit;
  154. value := prop.GetValue(aInstance);
  155. end;
  156. if value.Kind = TTypeKind.tkClass then rType := fCtx.GetType(value.AsObject.ClassType)
  157. else if value.Kind = TTypeKind.tkRecord then rtype := fCtx.GetType(value.TypeInfo);
  158. until i < 0;
  159. Result := prop;
  160. end;
  161. class function TRTTI.PathExists(aInstance: TObject; const aPropertyPath: string) : Boolean;
  162. var
  163. proppath : string;
  164. propname : string;
  165. i : Integer;
  166. value : TValue;
  167. rtype : TRttiType;
  168. rprop : TRttiProperty;
  169. {$IFNDEF FPC}
  170. rfield : TRttiField;
  171. {$ENDIF}
  172. lastsegment : Boolean;
  173. begin
  174. if not Assigned(aInstance) then Exit;
  175. lastsegment := False;
  176. proppath := aPropertyPath;
  177. rtype := fCtx.GetType(aInstance.ClassType);
  178. repeat
  179. Result := False;
  180. i := proppath.IndexOf('.');
  181. if i > -1 then
  182. begin
  183. propname := Copy(proppath,1,i);
  184. Delete(proppath,1,i+1);
  185. end
  186. else
  187. begin
  188. propname := proppath;
  189. lastsegment := True;
  190. end;
  191. if rtype.TypeKind = TTypeKind.tkRecord then
  192. begin
  193. {$IFNDEF FPC}
  194. rfield := rtype.GetField(propname);
  195. if rfield = nil then Exit
  196. else
  197. begin
  198. value := rfield.GetValue(value.GetReferenceToRawData);
  199. Result := True;
  200. end;
  201. {$ELSE}
  202. raise ERTTIError.Create('FPC not supports record fields in RTTI');
  203. {$ENDIF}
  204. end
  205. else
  206. begin
  207. rprop := rtype.GetProperty(propname);
  208. if rprop = nil then Exit
  209. else
  210. begin
  211. value := rprop.GetValue(aInstance);
  212. Result := True;
  213. end;
  214. end;
  215. if not lastsegment then
  216. begin
  217. if value.Kind = TTypeKind.tkClass then rType := fCtx.GetType(value.AsObject.ClassType)
  218. else if value.Kind = TTypeKind.tkRecord then rtype := fCtx.GetType(value.TypeInfo);
  219. end;
  220. until lastsegment;
  221. end;
  222. class function TRTTI.GetPathValue(aInstance: TObject; const aPropertyPath: string): TValue;
  223. var
  224. proppath : string;
  225. propname : string;
  226. i : Integer;
  227. value : TValue;
  228. rtype : TRttiType;
  229. rprop : TRttiProperty;
  230. {$IFNDEF FPC}
  231. rfield : TRttiField;
  232. {$ENDIF}
  233. lastsegment : Boolean;
  234. begin
  235. Result := nil;
  236. if not Assigned(aInstance) then Exit;
  237. lastsegment := False;
  238. proppath := aPropertyPath;
  239. rtype := fCtx.GetType(aInstance.ClassType);
  240. {$IFDEF FPC}
  241. value := aInstance;
  242. {$ENDIF}
  243. repeat
  244. i := proppath.IndexOf('.');
  245. if i > -1 then
  246. begin
  247. propname := Copy(proppath,1,i);
  248. Delete(proppath,1,i+1);
  249. end
  250. else
  251. begin
  252. propname := proppath;
  253. lastsegment := True;
  254. end;
  255. if rtype.TypeKind = TTypeKind.tkRecord then
  256. begin
  257. {$IFNDEF FPC}
  258. rfield := rtype.GetField(propname);
  259. if rfield = nil then raise ERTTIError.CreateFmt('Field "%s" not found in record',[propname])
  260. else value := rfield.GetValue(value.GetReferenceToRawData);
  261. {$ELSE}
  262. raise ERTTIError.Create('FPC not supports record fields in RTTI');
  263. {$ENDIF}
  264. end
  265. else
  266. begin
  267. rprop := rtype.GetProperty(propname);
  268. if rprop = nil then raise ERTTIError.CreateFmt('Property "%s" not found in object',[propname])
  269. {$IFNDEF FPC}
  270. else value := rprop.GetValue(aInstance);
  271. {$ELSE}
  272. else
  273. begin
  274. if rprop.PropertyType.IsInstance then value := GetObjectProp(value.AsObject,propname)
  275. else value := rprop.GetValue(value.AsObject);
  276. end;
  277. {$ENDIF}
  278. end;
  279. if not lastsegment then
  280. begin
  281. if value.Kind = TTypeKind.tkClass then rType := fCtx.GetType(value.AsObject.ClassType)
  282. else if value.Kind = TTypeKind.tkRecord then rtype := fCtx.GetType(value.TypeInfo);
  283. end;
  284. until lastsegment;
  285. Result := value;
  286. end;
  287. class procedure TRTTI.SetPathValue(aInstance: TObject; const aPropertyPath: string; aValue : TValue);
  288. var
  289. proppath : string;
  290. propname : string;
  291. i : Integer;
  292. value : TValue;
  293. rtype : TRttiType;
  294. rprop : TRttiProperty;
  295. {$IFNDEF FPC}
  296. rfield : TRttiField;
  297. {$ENDIF}
  298. lastsegment : Boolean;
  299. begin
  300. if not Assigned(aInstance) then Exit;
  301. lastsegment := False;
  302. proppath := aPropertyPath;
  303. rtype := fCtx.GetType(aInstance.ClassType);
  304. repeat
  305. i := proppath.IndexOf('.');
  306. if i > -1 then
  307. begin
  308. propname := Copy(proppath,1,i);
  309. Delete(proppath,1,i+1);
  310. end
  311. else
  312. begin
  313. propname := proppath;
  314. lastsegment := True;
  315. end;
  316. if rtype.TypeKind = TTypeKind.tkRecord then
  317. begin
  318. {$IFNDEF FPC}
  319. rfield := rtype.GetField(propname);
  320. if rfield = nil then raise ERTTIError.CreateFmt('Field "%s" not found in record',[propname])
  321. else
  322. begin
  323. if lastsegment then rfield.SetValue(value.GetReferenceToRawData,aValue)
  324. else value := rfield.GetValue(value.GetReferenceToRawData);
  325. end;
  326. {$ELSE}
  327. raise ERTTIError.Create('FPC not supports record fields in RTTI');
  328. {$ENDIF}
  329. end
  330. else
  331. begin
  332. rprop := rtype.GetProperty(propname);
  333. if rprop = nil then raise ERTTIError.CreateFmt('Property "%s" not found in object',[propname])
  334. else
  335. begin
  336. if lastsegment then rprop.SetValue(aInstance,aValue)
  337. else value := rprop.GetValue(aInstance);
  338. end;
  339. end;
  340. if not lastsegment then
  341. begin
  342. if value.Kind = TTypeKind.tkClass then rType := fCtx.GetType(value.AsObject.ClassType)
  343. else if value.Kind = TTypeKind.tkRecord then rtype := fCtx.GetType(value.TypeInfo);
  344. end;
  345. until lastsegment;
  346. end;
  347. class function TRTTI.GetPropertyValue(aInstance: TObject; const aPropertyName: string): TValue;
  348. var
  349. rprop : TRttiProperty;
  350. begin
  351. rprop := GetProperty(aInstance,aPropertyName);
  352. if rprop <> nil then
  353. begin
  354. {$IFNDEF FPC}
  355. Result := rprop.GetValue(aInstance);
  356. {$ELSE}
  357. if rprop.PropertyType.IsInstance then Result := GetObjectProp(aInstance,aPropertyName)
  358. else Result := rprop.GetValue(aInstance);
  359. {$ENDIF}
  360. end;
  361. end;
  362. class function TRTTI.GetPropertyValue(aTypeInfo: Pointer; const aPropertyName: string): TValue;
  363. var
  364. rprop : TRttiProperty;
  365. begin
  366. rprop := GetProperty(aTypeInfo,aPropertyName);
  367. if rprop <> nil then
  368. begin
  369. {$IFNDEF FPC}
  370. Result := rprop.GetValue(aTypeInfo);
  371. {$ELSE}
  372. if rprop.PropertyType.IsInstance then Result := GetObjectProp(aTypeInfo,aPropertyName)
  373. else Result := rprop.GetValue(aTypeInfo);
  374. {$ENDIF}
  375. end;
  376. end;
  377. class function TRTTI.GetType(aTypeInfo: Pointer): TRttiType;
  378. begin
  379. Result := fCtx.GetType(aTypeInfo);
  380. end;
  381. class function TRTTI.PropertyExists(aTypeInfo: Pointer; const aPropertyName: string): Boolean;
  382. begin
  383. Result := fCtx.GetType(aTypeInfo).GetProperty(aPropertyName) <> nil;
  384. end;
  385. class procedure TRTTI.SetPropertyValue(aInstance: TObject; const aPropertyName: string; aValue: TValue);
  386. var
  387. rprop : TRttiProperty;
  388. begin
  389. rprop := GetProperty(aInstance,aPropertyName);
  390. if rprop <> nil then rprop.SetValue(aInstance,aValue);
  391. end;
  392. {$IFNDEF FPC}
  393. class function TRTTI.FindClass(const aClassName: string): TClass;
  394. var
  395. rType : TRttiType;
  396. rList : TArray<TRttiType>;
  397. begin
  398. Result := nil;
  399. rList := fCtx.GetTypes;
  400. for rType in rList do
  401. begin
  402. if (rType.IsInstance) and (aClassName.EndsWith(rType.Name)) then
  403. begin
  404. Result := rType.AsInstance.MetaClassType;
  405. Break;
  406. end;
  407. end;
  408. end;
  409. {$ENDIF}
  410. end.