Quick.RTTI.Utils.pas 13 KB

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