Skip to content

Commit 4b0bd23

Browse files
author
Jani Giannoudis
committed
added case object to map c# types to cases with support to split a case into sub types
case object includes support for namespaces to map c# class to different cases payroll function: added queries for case object and case object values payroll function: added queries for raw case objects, single and time series payroll function: added division id and calendar period query case change function: added methods to get/set the case change object case change function: added methods to mark a case field as visible/hidden renamed time period to hour period case change function: added methods to show/hide a case field payroll function: added access to the division id payroll function runtime: added support to query multiple case values client scripting: fixed case validate attribute access updated version to 0.9.0-beta.6
1 parent 519431c commit 4b0bd23

16 files changed

+988
-242
lines changed

Client.Scripting/CaseObject.cs

+323
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
/* CaseObject */
2+
3+
using System;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Collections.Generic;
7+
8+
namespace PayrollEngine.Client.Scripting;
9+
10+
/// <summary>Case object attribute</summary>
11+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
12+
public class CaseObjectAttribute(string ns) : Attribute
13+
{
14+
/// <summary>Object namespace</summary>
15+
public string Ns { get; } = ns;
16+
}
17+
18+
/// <summary>Case field ignore attribute</summary>
19+
[AttributeUsage(AttributeTargets.Property)]
20+
public class CaseFieldIgnoreAttribute : Attribute;
21+
22+
/// <summary>Case object extension methods</summary>
23+
public static class CaseObjectExtensions
24+
{
25+
/// <summary>Get member namespace</summary>
26+
/// <param name="member">Member</param>
27+
public static string GetNamespace(this MemberInfo member) =>
28+
(member.GetCustomAttribute(typeof(CaseObjectAttribute)) as CaseObjectAttribute)?.Ns;
29+
30+
/// <summary>Get case field name</summary>
31+
/// <param name="property">Property</param>
32+
public static string GetCaseFieldName(this PropertyInfo property) =>
33+
GetNamespace(property) + property.Name;
34+
}
35+
36+
/// <summary>Case object</summary>
37+
public interface ICaseObject
38+
{
39+
/// <summary>Get case object by property name</summary>
40+
T GetObject<T>(string propertyName) where T : class, ICaseObject;
41+
42+
/// <summary>Get case objects</summary>
43+
List<T> GetObjects<T>() where T : class, ICaseObject;
44+
45+
/// <summary>Get case field name</summary>
46+
string GetCaseFieldName(string propertyName);
47+
48+
/// <summary>Get case object value by case field name</summary>
49+
object GetValue(string caseFieldName);
50+
51+
/// <summary>Set case object value by case field name</summary>
52+
void SetValue(string caseFieldName, object value);
53+
}
54+
55+
/// <summary>Case object/// </summary>
56+
public abstract class CaseObject : ICaseObject
57+
{
58+
59+
#region Objects
60+
61+
/// <inheritdoc />
62+
public T GetObject<T>(string propertyName) where T : class, ICaseObject =>
63+
GetObject<T>(GetProperty(propertyName), this);
64+
65+
/// <inheritdoc />
66+
public List<T> GetObjects<T>() where T : class, ICaseObject
67+
{
68+
var objects = new List<T>();
69+
foreach (var propertyInfo in GetType().GetProperties())
70+
{
71+
var obj = GetObject<T>(propertyInfo, this);
72+
if (obj != null)
73+
{
74+
objects.Add(obj);
75+
}
76+
}
77+
return objects;
78+
}
79+
80+
#endregion
81+
82+
#region Case Field
83+
84+
/// <inheritdoc />
85+
public string GetCaseFieldName(string propertyName) =>
86+
GetProperty(propertyName).GetCaseFieldName();
87+
88+
/// <inheritdoc />
89+
public object GetValue(string caseFieldName)
90+
{
91+
var propertyObject = FindDeclaringObject(caseFieldName, null, this);
92+
if (propertyObject.Declaring == null)
93+
{
94+
throw new ScriptException($"Unknown object for case field {caseFieldName}");
95+
}
96+
return propertyObject.Property.GetValue(propertyObject.Declaring);
97+
}
98+
99+
/// <inheritdoc />
100+
public void SetValue(string caseFieldName, object value)
101+
{
102+
var propertyObject = FindDeclaringObject(caseFieldName, null, this);
103+
if (propertyObject.Declaring == null)
104+
{
105+
throw new ScriptException($"Unknown object case field {caseFieldName}");
106+
}
107+
108+
// read only property
109+
if (!propertyObject.Property.CanWrite)
110+
{
111+
return;
112+
}
113+
114+
try
115+
{
116+
propertyObject.Property.SetValue(propertyObject.Declaring, value);
117+
}
118+
catch (Exception exception)
119+
{
120+
throw new ScriptException($"Case object field {caseFieldName} error value {value}.", exception);
121+
}
122+
}
123+
124+
/// <summary>Get property by name</summary>
125+
/// <param name="propertyName">Property name</param>
126+
protected PropertyInfo GetProperty(string propertyName) =>
127+
GetProperty(GetType(), propertyName);
128+
129+
/// <summary>Find declaring object with property using a recursive search</summary>
130+
/// <param name="caseFieldName">Case field name</param>
131+
/// <param name="namespace">The namespace</param>
132+
/// <param name="caseObject">Case object</param>
133+
private (PropertyInfo Property, ICaseObject Declaring) FindDeclaringObject(string caseFieldName, string @namespace, ICaseObject caseObject)
134+
{
135+
// fallback namespace
136+
if (string.IsNullOrWhiteSpace(@namespace))
137+
{
138+
@namespace = caseObject.GetType().GetNamespace();
139+
}
140+
141+
var properties = GetProperties(caseObject.GetType(), recursive: false);
142+
foreach (var property in properties)
143+
{
144+
var propertyCaseFieldName = @namespace + property.Property.Name;
145+
146+
// matching property with case object
147+
if (string.Equals(propertyCaseFieldName, caseFieldName))
148+
{
149+
return (property.Property, caseObject);
150+
}
151+
152+
// child object
153+
if (property.Property.GetValue(caseObject) is ICaseObject childValue)
154+
{
155+
// recursive call
156+
var childProperty = FindDeclaringObject(caseFieldName, property.Property.GetNamespace(), childValue);
157+
if (childProperty.Property != null)
158+
{
159+
return childProperty;
160+
}
161+
}
162+
}
163+
return new(null, null);
164+
}
165+
166+
#endregion
167+
168+
#region Case Field Reflection
169+
170+
/// <summary>Get case field name</summary>
171+
public static string GetCaseFieldName<T>(string propertyName)
172+
where T : class, ICaseObject =>
173+
GetProperty(typeof(T), propertyName)?.GetCaseFieldName();
174+
175+
/// <summary>Get case field names</summary>
176+
/// <param name="recursive">Recursive objects (default: true)</param>
177+
/// <param name="writeable">Writeable properties only</param>
178+
public static List<string> GetCaseFieldNames<T>(bool recursive = true, bool writeable = false)
179+
where T : class, ICaseObject =>
180+
GetProperties(typeof(T), recursive, writeable).Select(x => x.CaseFieldName).ToList();
181+
182+
#endregion
183+
184+
#region Property Reflection
185+
186+
/// <summary>Get object properties</summary>
187+
/// <param name="recursive">Recursive objects (default: true)</param>
188+
/// <param name="writeable">Writeable properties only</param>
189+
public static List<(PropertyInfo Property, string CaseFieldName)> GetProperties<T>(bool recursive = true, bool writeable = false)
190+
where T : class, ICaseObject =>
191+
GetProperties(typeof(T), recursive, writeable);
192+
193+
/// <summary>Get object properties</summary>
194+
/// <param name="type">Object type</param>
195+
/// <param name="recursive">Recursive objects (default: true)</param>
196+
/// <param name="writeable">Writeable properties only</param>
197+
public static List<(PropertyInfo Property, string CaseFieldName)> GetProperties(Type type, bool recursive = true,
198+
bool writeable = false) =>
199+
FindProperties(type, recursive, writeable, type.GetNamespace());
200+
201+
/// <summary>Find object properties with recursive search</summary>
202+
/// <param name="type">Object type</param>
203+
/// <param name="recursive">Recursive objects (default: true)</param>
204+
/// <param name="writeable">Writeable properties only</param>
205+
/// <param name="namespace">The namespace</param>
206+
private static List<(PropertyInfo Property, string CaseFieldName)> FindProperties(Type type, bool recursive,
207+
bool writeable, string @namespace)
208+
{
209+
// fallback namespace
210+
if (string.IsNullOrWhiteSpace(@namespace))
211+
{
212+
@namespace = type.GetNamespace();
213+
}
214+
215+
var properties = new List<(PropertyInfo Property, string CaseFieldName)>();
216+
foreach (var propertyInfo in type.GetProperties())
217+
{
218+
if (recursive && IsObjectProperty(propertyInfo))
219+
{
220+
// child object, recursive call
221+
properties.AddRange(FindProperties(propertyInfo.PropertyType, true, writeable, propertyInfo.GetNamespace()));
222+
}
223+
else if (IsFieldProperty(propertyInfo, writeable))
224+
{
225+
// case field
226+
properties.Add(new(propertyInfo, @namespace + propertyInfo.GetCaseFieldName()));
227+
}
228+
}
229+
return properties;
230+
}
231+
232+
private static PropertyInfo GetProperty(Type type, string propertyName)
233+
{
234+
if (string.IsNullOrWhiteSpace(propertyName))
235+
{
236+
throw new ArgumentException(nameof(propertyName));
237+
}
238+
var property = type.GetProperty(propertyName);
239+
if (property == null)
240+
{
241+
throw new ScriptException($"Unknown property {propertyName} in type {type}.");
242+
}
243+
return IgnoredProperty(property) ? null : property;
244+
}
245+
246+
#endregion
247+
248+
#region Object Reflection
249+
250+
private static T GetObject<T>(PropertyInfo property, object source) where T : class, ICaseObject =>
251+
IsObjectProperty(property) ? property.GetValue(source) as T : null;
252+
253+
private static bool IgnoredProperty(PropertyInfo property) =>
254+
property.GetCustomAttribute(typeof(CaseFieldIgnoreAttribute)) != null;
255+
256+
private static bool IsObjectProperty(PropertyInfo property)
257+
{
258+
// ignored property
259+
if (IgnoredProperty(property))
260+
{
261+
return false;
262+
}
263+
264+
// read property
265+
if (!property.CanRead)
266+
{
267+
return false;
268+
}
269+
270+
// missing public get
271+
var method = property.GetGetMethod(nonPublic: true);
272+
if (method == null || !method.IsPublic)
273+
{
274+
return false;
275+
}
276+
277+
// matching property type
278+
return typeof(ICaseObject).IsAssignableFrom(property.PropertyType);
279+
}
280+
281+
private static bool IsFieldProperty(PropertyInfo property, bool writeable)
282+
{
283+
// ignored property
284+
if (IgnoredProperty(property))
285+
{
286+
return false;
287+
}
288+
289+
// read property
290+
if (!property.CanRead)
291+
{
292+
return false;
293+
}
294+
295+
// missing public get
296+
var getMethod = property.GetGetMethod(nonPublic: true);
297+
if (getMethod == null || !getMethod.IsPublic)
298+
{
299+
return false;
300+
}
301+
302+
// writeable
303+
if (writeable && !property.CanWrite)
304+
{
305+
return false;
306+
}
307+
// write property
308+
if (property.CanWrite)
309+
{
310+
// missing public set
311+
var setMethod = property.GetSetMethod(nonPublic: true);
312+
if (setMethod == null || !setMethod.IsPublic)
313+
{
314+
return false;
315+
}
316+
}
317+
318+
return true;
319+
}
320+
321+
#endregion
322+
323+
}

0 commit comments

Comments
 (0)