-
Notifications
You must be signed in to change notification settings - Fork 8
/
DX.Utils.RTTI.pas
363 lines (330 loc) · 9.38 KB
/
DX.Utils.RTTI.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
/// <summary>
/// This unit provides RTTI based utility classes/functions
/// </summary>
unit DX.Utils.RTTI;
interface
uses
System.Classes, System.SysUtils, System.RTTI, DX.Classes.Strings;
type
EConstructorNotFound = class(Exception);
/// <summary>
/// Offers helper methods for TObject, mainly for simplified property and attribute access
/// </summary>
TObjectHelper = class helper for TObject
/// <summary>
/// Lists all properties of the given class instance as 'name = value'
/// pairs.
/// </summary>
/// <param name="AExcludes">
/// Excludes all pairs that match an item in AExcludes
/// </param>
function ListProperties(const AExcludes: TArray<string>): StringList; overload;
/// <summary>
/// Lists all properties of the given class instance as 'name = value'
/// </summary>
function ListProperties: StringList; overload;
/// <summary>
/// Returns true if the given class has the specified attribute attached.
/// </summary>
function HasAttribute(AAttribute: TClass): boolean;
function AttributeValue(AAttribute: TClass): string;
function GetAttribute(AAttribute: TClass): TCustomAttribute;
function HasProperty(APropertyName: string): boolean;
function GetProperty(APropertyName: string): TRttiProperty;
procedure SetProperty(APropertyName: string; AValue: TValue);
procedure CopyFrom(ASource: TObject);
end;
TRTTIPropertyHelper = class helper for TRttiProperty
public
/// <summary>
/// Sets the value of the field, specified by AFieldName. Works with
/// records!
/// </summary>
/// <param name="AInstance">
/// The containing instance
/// </param>
/// <param name="AFieldName">
/// Name of the field variable to set
/// </param>
/// <param name="AValue">
/// A TValue containing the value to be set
/// </param>
procedure SetFieldValue(
AInstance: Pointer;
const AFieldName: string;
AValue: TValue);
end;
TClassConstructor = class
private
/// <summary>
/// Utility class that has the ability to construct a given class (T) by
/// calling its parameter-less constructor. If T has no such constructor,
/// then an EConstructorNotFound exception is raised.
/// </summary>
class function GetConstructor(AClass: TRTTIType): TRttiMethod;
public
/// <summary>
/// Creates and returns an instance of T
/// </summary>
class function Construct<T: Class>: T;
/// <summary>
/// Creates a deep copy of AInstance, by Marshaling/Unmarshaling
/// it to/from Json.
/// </summary>
class function Clone(AInstance: TObject): TObject;
end;
implementation
uses
System.Json,
Data.DBXJsonReflect, DX.Classes.Attributes;
class function TClassConstructor.Clone(AInstance: TObject): TObject;
var
LMarshalObj: TJSONMarshal;
LUnMarshalObj: TJSONUnMarshal;
LJSONValue: TJSONValue;
begin
Result := nil;
LMarshalObj := TJSONMarshal.Create;
LUnMarshalObj := TJSONUnMarshal.Create;
try
LJSONValue := LMarshalObj.Marshal(AInstance);
try
if Assigned(LJSONValue) then
Result := LUnMarshalObj.Unmarshal(LJSONValue);
finally
LJSONValue.Free;
end;
finally
LMarshalObj.Free;
LUnMarshalObj.Free;
end;
end;
class function TClassConstructor.GetConstructor(AClass: TRTTIType): TRttiMethod;
var
LMethods: TArray<TRttiMethod>;
LMethod: TRttiMethod;
LParams: TArray<TRttiParameter>;
begin
Result := nil;
LMethods := AClass.GetMethods('Create');
for LMethod in LMethods do
begin
LParams := LMethod.GetParameters();
// Look for parameter-less constructor
if (Length(LParams) = 0) then
begin
Result := LMethod;
break;
end;
end;
if not Assigned(Result) then
raise EConstructorNotFound.CreateFmt('Class %s has no parameter-less constructor!', [AClass.QualifiedName]);
end;
{ TClassConstructor }
class function TClassConstructor.Construct<T>: T;
var
LInstance: TValue;
LContext: TRttiContext;
// LClass: TRttiType;
LClassType: TRTTIType;
LConstructor: TRttiMethod;
begin
LContext := TRttiContext.Create();
// LClass := LContext.GetType(TypeInfo(T));
LClassType := LContext.GetType(T);
LConstructor := GetConstructor(LClassType);
LInstance := LConstructor.Invoke(LClassType.AsInstance.MetaclassType, []);
Result := LInstance.AsObject as T;
end;
{ TObjectHelper }
function TObjectHelper.ListProperties(const AExcludes: TArray<string>): StringList;
var
LContext: TRttiContext;
LType: TRTTIType;
LProperties: TArray<TRttiProperty>;
LProperty: TRttiProperty;
begin
Result.Clear;
LContext := TRttiContext.Create;
try
LType := LContext.GetType(self.ClassType);
LProperties := LType.GetProperties;
for LProperty in LProperties do
begin
if not(AExcludes.Contains(LProperty.Name)) then
begin
var LValue := '';
if LProperty.IsReadable then
begin
LValue := ' = ' + LProperty.GetValue(self).ToString;
end;
Result.Add(LProperty.Name + LValue);
end;
end;
Result.Sort;
finally
LContext.Free;
end;
end;
function TObjectHelper.AttributeValue(AAttribute: TClass): string;
var
LAttribute: StringValueAttribute;
begin
Result := '';
if AAttribute.InheritsFrom(StringValueAttribute) then
begin
LAttribute := StringValueAttribute(GetAttribute(AAttribute));
if Assigned(LAttribute) then
begin
Result := LAttribute.Value;
end;
end;
end;
procedure TObjectHelper.CopyFrom(ASource: TObject);
var
LContext: TRttiContext;
LSelfType: TRTTIType;
LSelfProperties: TArray<TRttiProperty>;
LSelfProperty: TRttiProperty;
LSourceType: TRTTIType;
LSourceProperties: TArray<TRttiProperty>;
LSourceProperty: TRttiProperty;
LSourceValue: TValue;
begin
if Assigned(ASource) then
begin
LContext := TRttiContext.Create;
try
LSourceType := LContext.GetType(ASource.ClassType);
LSourceProperties := LSourceType.GetProperties;
LSelfType := LContext.GetType(self.ClassType);
LSelfProperties := LSelfType.GetProperties;
for LSelfProperty in LSelfProperties do
begin
if LSelfProperty.IsWritable then
begin
// Find in source and copy if type matches
for LSourceProperty in LSourceProperties do
begin
if (LSourceProperty.IsReadable)
and (LSourceProperty.Name = LSelfProperty.Name)
and (LSourceProperty.PropertyType = LSelfProperty.PropertyType)
then
begin
LSourceValue := LSourceProperty.GetValue(ASource);
LSelfProperty.SetValue(self, LSourceValue);
break;
end;
end;
end;
end;
finally
LContext.Free;
end;
end;
end;
function TObjectHelper.GetAttribute(AAttribute: TClass): TCustomAttribute;
var
LContext: TRttiContext;
LConfigType: TRTTIType;
LAttributes: TArray<TCustomAttribute>;
LAttribute: TCustomAttribute;
begin
Result := nil;
LContext := TRttiContext.Create;
try
LConfigType := LContext.GetType(self.ClassType);
LAttributes := LConfigType.GetAttributes;
for LAttribute in LAttributes do
begin
if LAttribute.ClassType = AAttribute then
begin
Result := LAttribute;
break;
end;
end;
finally
LContext.Free;
end;
end;
function TObjectHelper.GetProperty(APropertyName: string): TRttiProperty;
var
LContext: TRttiContext;
LConfigType: TRTTIType;
LProperties: TArray<TRttiProperty>;
LProperty: TRttiProperty;
begin
Result := nil;
LContext := TRttiContext.Create;
try
LConfigType := LContext.GetType(self.ClassType);
LProperties := LConfigType.GetProperties;
for LProperty in LProperties do
begin
if LProperty.Name.ToLower = APropertyName.ToLower then
begin
Result := LProperty;
break;
end;
end;
finally
LContext.Free;
end;
end;
function TObjectHelper.HasAttribute(AAttribute: TClass): boolean;
var
LContext: TRttiContext;
LConfigType: TRTTIType;
LAttributes: TArray<TCustomAttribute>;
LAttribute: TCustomAttribute;
begin
Result := false;
LContext := TRttiContext.Create;
try
LConfigType := LContext.GetType(self.ClassType);
LAttributes := LConfigType.GetAttributes;
for LAttribute in LAttributes do
begin
if LAttribute is AAttribute then
begin
Result := true;
break;
end;
end;
finally
LContext.Free;
end;
end;
function TObjectHelper.HasProperty(APropertyName: string): boolean;
begin
Result := GetProperty(APropertyName) <> nil;
end;
function TObjectHelper.ListProperties: StringList;
begin
Result := self.ListProperties([]);
end;
procedure TObjectHelper.SetProperty(APropertyName: string; AValue: TValue);
begin
GetProperty(APropertyName).SetValue(self, AValue);
end;
{ TRTTIPropertyHelper }
procedure TRTTIPropertyHelper.SetFieldValue(
AInstance: Pointer;
const AFieldName: string;
AValue: TValue);
var
LField: TRTTIField;
LValue: TValue;
begin
// The value of the property, which is considered a record
LValue := self.GetValue(AInstance);
// A field of the record
LField := self.PropertyType.GetField(AFieldName);
if LField = nil then
raise EInvalidCast.Create('Invalid type - field "%s" not found!');
// set the field's value
LField.SetValue(LValue.GetReferenceToRawData, AValue);
// finally set the property value with the modified field value(s)
self.SetValue(AInstance, LValue);
end;
end.