Skip to content

Commit 6cd2641

Browse files
committed
Support function object types in Ocl
1 parent 9afc81c commit 6cd2641

File tree

12 files changed

+432
-41
lines changed

12 files changed

+432
-41
lines changed

source/Ocl/Converters/OclConverter.cs

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Reflection;
5+
using System.Text;
56

67
namespace Octopus.Ocl.Converters
78
{
89
public abstract class OclConverter : IOclConverter
910
{
11+
/// <summary>
12+
/// Returns true if the converter can be used for the provided type
13+
/// </summary>
14+
/// <param name="type">The model type being converted</param>
1015
public abstract bool CanConvert(Type type);
1116

1217
public virtual IEnumerable<IOclElement> ToElements(OclConversionContext context, PropertyInfo? propertyInfo, object obj)
@@ -17,6 +22,13 @@ public virtual IEnumerable<IOclElement> ToElements(OclConversionContext context,
1722
: Array.Empty<IOclElement>();
1823
}
1924

25+
/// <summary>
26+
/// Converts the provided object to an OCL root document.
27+
/// </summary>
28+
/// <param name="context"></param>
29+
/// <param name="obj"></param>
30+
/// <returns></returns>
31+
/// <exception cref="NotSupportedException"></exception>
2032
public virtual OclDocument ToDocument(OclConversionContext context, object obj)
2133
=> throw new NotSupportedException("This type does not support conversion to the OCL root document");
2234

@@ -32,20 +44,25 @@ protected virtual string GetName(OclConversionContext context, PropertyInfo? pro
3244
protected virtual IEnumerable<IOclElement> GetElements(object obj, IEnumerable<PropertyInfo> properties, OclConversionContext context)
3345
{
3446
var elements = from p in properties
35-
from element in context.ToElements(p, p.GetValue(obj))
47+
from element in PropertyToElements(obj, context, p)
3648
orderby
3749
element is OclBlock,
3850
element.Name
3951
select element;
4052
return elements;
4153
}
4254

55+
protected virtual IEnumerable<IOclElement> PropertyToElements(object obj, OclConversionContext context, PropertyInfo p)
56+
=> context.ToElements(p, p.GetValue(obj));
57+
58+
4359
protected virtual IReadOnlyList<IOclElement> SetProperties(
4460
OclConversionContext context,
4561
IEnumerable<IOclElement> elements,
4662
object target,
4763
IReadOnlyList<PropertyInfo> properties)
4864
{
65+
4966
var notFound = new List<IOclElement>();
5067
foreach (var element in elements)
5168
{
@@ -68,52 +85,70 @@ protected virtual IReadOnlyList<IOclElement> SetProperties(
6885
if (!propertyToSet.CanWrite)
6986
throw new OclException($"The property '{propertyToSet.Name}' on '{target.GetType().Name}' does not have a setter");
7087

71-
propertyToSet.SetValue(target, CoerceValue(valueToSet, propertyToSet.PropertyType));
88+
propertyToSet.SetValue(target, CoerceValue(context, valueToSet, propertyToSet.PropertyType));
7289
}
7390
}
7491
}
7592

7693
return notFound;
7794
}
7895

79-
object? CoerceValue(object? valueToSet, Type type)
96+
object? CoerceValue(OclConversionContext context, object? sourceValue, Type targetType)
8097
{
81-
if (valueToSet is OclStringLiteral literal)
82-
valueToSet = literal.Value;
98+
if (sourceValue is OclStringLiteral literal)
99+
sourceValue = literal.Value;
100+
101+
if (sourceValue is OclFunctionCall functionCall)
102+
{
103+
var result = context.GetFunctionCallFor(functionCall.Name).ToValue(functionCall);
104+
return CoerceValue(context, result, targetType);
105+
}
83106

84-
if (valueToSet == null)
107+
if (sourceValue == null)
85108
return null;
86109

87-
if (type.IsInstanceOfType(valueToSet))
88-
return valueToSet;
110+
if (targetType.IsInstanceOfType(sourceValue))
111+
return sourceValue;
89112

90-
if (valueToSet is Dictionary<string, object?> dict)
113+
if (sourceValue is Dictionary<string, object?> dict)
91114
{
92-
if (type.IsAssignableFrom(typeof(Dictionary<string, string>)))
93-
return dict.ToDictionary(kvp => kvp.Key, kvp => (string?)CoerceValue(kvp.Value, typeof(string)));
115+
if (targetType.IsAssignableFrom(typeof(Dictionary<string, string>)))
116+
return dict.ToDictionary(kvp => kvp.Key, kvp => (string?)CoerceValue(context, kvp.Value, typeof(string)));
94117

95-
throw new OclException($"Could not coerce dictionary to {type.Name}. Only Dictionary<string, string> and Dictionary<string, object?> are supported.");
118+
throw new OclException($"Could not coerce dictionary to {targetType.Name}. Only Dictionary<string, string> and Dictionary<string, object?> are supported.");
96119
}
97120

98-
if (type == typeof(string) && valueToSet.GetType().IsPrimitive)
99-
return valueToSet.ToString();
121+
if (targetType == typeof(string))
122+
{
123+
if (sourceValue.GetType().IsPrimitive)
124+
return sourceValue.ToString();
100125

126+
if (sourceValue is byte[] bytes)
127+
return Encoding.UTF8.GetString(bytes);
128+
}
129+
101130
object? FromArray<T>()
102131
{
103-
if (valueToSet is T[] array)
132+
if (sourceValue is T[] array)
104133
{
105-
if (type == typeof(List<T>))
134+
if (targetType == typeof(List<T>))
106135
return array.ToList();
107-
if (type == typeof(HashSet<T>))
136+
if (targetType == typeof(HashSet<T>))
108137
return array.ToHashSet();
109138
}
110139

111140
return null;
112141
}
113142

114-
return FromArray<string>() ?? FromArray<decimal>() ?? FromArray<int>() ?? throw new Exception($"Could not coerce value of type {valueToSet.GetType().Name} to {type.Name}");
143+
return FromArray<string>() ?? FromArray<decimal>() ?? FromArray<int>() ?? throw new Exception($"Could not coerce value of type {sourceValue.GetType().Name} to {targetType.Name}");
115144
}
116145

146+
/// <summary>
147+
/// Get the properties for the given type.
148+
/// TODO: The virtual accessor can probably be removed and replaced with a ShouldSerialize method that seems to be used.
149+
/// </summary>
150+
/// <param name="type"></param>
151+
/// <returns></returns>
117152
protected virtual IEnumerable<PropertyInfo> GetProperties(Type type)
118153
{
119154
var defaultProperties = type.GetDefaultMembers().OfType<PropertyInfo>();
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
using System.Reflection;
3+
4+
namespace Octopus.Ocl.FunctionCalls
5+
{
6+
public interface IFunctionCall
7+
{
8+
string Name {get;}
9+
object? ToValue(OclFunctionCall functionCall);
10+
OclFunctionCall? ToOclFunctionCall(object obj, PropertyInfo propertyInfo);
11+
12+
OclFunctionCall? ToOclFunctionCall(object[] arguments);
13+
}
14+
}

source/Ocl/OclAttribute.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public object? Value
4747
set
4848
{
4949
if (value != null && !IsSupportedValueType(value.GetType()))
50-
throw new OclException($"The type {value.GetType().FullName} is not a support value type OCL attribute value");
50+
throw new OclException($"The type {value.GetType().FullName} is not a supported value type OCL attribute value");
5151
this.value = value;
5252
}
5353
}
@@ -64,9 +64,13 @@ bool IsNullableSupportedValueType()
6464
IsObjectDictionary(type) ||
6565
IsStringDictionary(type) ||
6666
IsNullableSupportedValueType() ||
67-
IsSupportedValueCollectionType(type);
67+
IsSupportedValueCollectionType(type) ||
68+
IsFunctionCall(type);
6869
}
6970

71+
internal static bool IsFunctionCall(Type type)
72+
=> typeof(OclFunctionCall).IsAssignableFrom(type);
73+
7074
internal static bool IsObjectDictionary(Type type)
7175
=> typeof(IEnumerable<KeyValuePair<string, object?>>).IsAssignableFrom(type);
7276

source/Ocl/OclConversionContext.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,32 @@
33
using System.Linq;
44
using System.Reflection;
55
using Octopus.Ocl.Converters;
6+
using Octopus.Ocl.FunctionCalls;
67
using Octopus.Ocl.Namers;
78

89
namespace Octopus.Ocl
910
{
11+
/*public class FileFunction: IFunctionCall
12+
{
13+
public static string FnName = "file";
14+
public string Name { get; } = FnName;
15+
16+
17+
public object? ToValue(OclFunctionCall functionCall)
18+
=> throw new NotImplementedException();
19+
20+
public OclFunctionCall? ToOclFunctionCall(object obj, PropertyInfo propertyInfo)
21+
=> throw new NotImplementedException();
22+
23+
public OclFunctionCall? ToOclFunctionCall(object[] arguments)
24+
=> throw new NotImplementedException();
25+
}*/
26+
1027
public class OclConversionContext
1128
{
1229
readonly IReadOnlyList<IOclConverter> converters;
30+
31+
readonly IReadOnlyList<IFunctionCall> functions;
1332

1433
public OclConversionContext(OclSerializerOptions options)
1534
{
@@ -24,6 +43,12 @@ public OclConversionContext(OclSerializerOptions options)
2443
new DefaultBlockOclConverter()
2544
})
2645
.ToArray();
46+
47+
functions = options.Functions.Concat(new IFunctionCall[]
48+
{
49+
//TODO: Add some built-in functions
50+
}).ToArray();
51+
2752
Namer = options.Namer;
2853
}
2954

@@ -38,6 +63,18 @@ public IOclConverter GetConverterFor(Type type)
3863
throw new Exception("Could not find a converter for " + type.FullName);
3964
}
4065

66+
public IFunctionCall GetFunctionCallFor(string name)
67+
{
68+
var fnCall = functions.FirstOrDefault(f => f.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
69+
if (fnCall == null)
70+
{
71+
throw new OclException($"Call to unknown function. "
72+
+ $"There is no function named \"{name}\"");
73+
}
74+
75+
return fnCall;
76+
}
77+
4178
internal IEnumerable<IOclElement> ToElements(PropertyInfo? propertyInfo, object? value)
4279
{
4380
if (value == null)

source/Ocl/OclFunctionCall.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System.Collections.Generic;
2+
using System.Diagnostics;
3+
using System.Linq;
4+
5+
namespace Octopus.Ocl
6+
{
7+
[DebuggerDisplay("{Name}({Arguments})", Name = "OclFunctionCall")]
8+
public class OclFunctionCall : IOclElement
9+
{
10+
string name;
11+
IEnumerable<object?> arguments;
12+
13+
public OclFunctionCall(string name, IEnumerable<object?> arguments)
14+
{
15+
this.name = Name = name; // Make the compiler happy
16+
this.arguments = arguments;
17+
}
18+
19+
public string Name
20+
{
21+
get => name;
22+
set
23+
{
24+
if (string.IsNullOrWhiteSpace(value))
25+
throw new OclException("FunctionCalls must have an identifier name");
26+
name = value;
27+
}
28+
}
29+
30+
/// <remarks>
31+
/// The attribute value is given as an expression, which is retained literally for later evaluation by the calling application.
32+
/// </remarks>
33+
public IEnumerable<object?> Arguments
34+
{
35+
get => arguments;
36+
set
37+
{
38+
var invalidArg = arguments.Where(a => a != null && !OclAttribute.IsSupportedValueType(a.GetType()))
39+
.Select(t => t?.GetType().FullName).Distinct().ToArray();
40+
if(invalidArg.Any())
41+
{
42+
var msg = (invalidArg.Length == 1) ?
43+
$"The type {invalidArg} is not a supported value type for an OCL function call argument" :
44+
$"The types {string.Join(',', invalidArg)} are not a supported value types for an OCL function call argument";
45+
throw new OclException(msg);
46+
}
47+
48+
this.arguments = value;
49+
}
50+
}
51+
}
52+
}

source/Ocl/OclSerializerOptions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using Octopus.Ocl.FunctionCalls;
34
using Octopus.Ocl.Namers;
45

56
namespace Octopus.Ocl
@@ -13,6 +14,7 @@ public class OclSerializerOptions
1314
public int IndentDepth { get; set; } = 4;
1415
public string DefaultHeredocTag { get; set; } = "EOT";
1516
public List<IOclConverter> Converters { get; set; } = new List<IOclConverter>();
17+
public List<IFunctionCall> Functions { get; set; } = new List<IFunctionCall>();
1618
public IOclNamer Namer { get; set; } = new SnakeCaseOclNamer();
1719
}
1820
}

source/Ocl/OclWriter.cs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -203,26 +203,44 @@ void WriteValue(object? value)
203203
return;
204204
}
205205

206+
if(OclAttribute.IsFunctionCall(valueType))
207+
{
208+
WriteValue((OclFunctionCall)value);
209+
return;
210+
}
211+
206212
if (OclAttribute.IsSupportedValueCollectionType(valueType))
207213
{
208-
var enumerable = (IEnumerable)value;
209214
writer.Write('[');
210-
var isFirst = true;
211-
foreach (var item in enumerable)
212-
{
213-
if (!isFirst)
214-
writer.Write(", ");
215-
isFirst = false;
216-
WriteValue(item);
217-
}
218-
215+
Write((IEnumerable)value);
219216
writer.Write(']');
220217
return;
221218
}
222219

223220
throw new InvalidOperationException($"The type {value.GetType().FullName} is not a valid attribute value and can not be serialized");
224221
}
225222

223+
void Write(IEnumerable enumerable)
224+
{
225+
226+
var isFirst = true;
227+
foreach (var item in enumerable)
228+
{
229+
if (!isFirst)
230+
writer.Write(", ");
231+
isFirst = false;
232+
WriteValue(item);
233+
}
234+
}
235+
236+
void WriteValue(OclFunctionCall functionCall)
237+
{
238+
writer.Write(functionCall.Name);
239+
writer.Write("(");
240+
Write((IEnumerable)functionCall.Arguments);
241+
writer.Write(")");
242+
}
243+
226244
void WriteValue(OclStringLiteral literal)
227245
{
228246
if (literal.Format == OclStringLiteralFormat.SingleLine)

0 commit comments

Comments
 (0)