From 2389b933e2c1dad9934df3458d3d109504ef85a9 Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Wed, 24 Jan 2024 11:35:22 -0600 Subject: [PATCH] cleanup/nullable-ize Message/ classes (#822) --- AcceptanceTest/TestBase.cs | 4 +- Examples/FixToJson/Examples.FixToJson.csproj | 4 +- Examples/FixToJson/Program.cs | 52 +- Examples/JsonToFix/Examples.JsonToFix.csproj | 1 - Examples/JsonToFix/Program.cs | 41 +- QuickFIXn/DataDictionary/DataDictionary.cs | 3 +- QuickFIXn/Fields/IField.cs | 5 +- QuickFIXn/Message/FieldMap.cs | 216 ++---- QuickFIXn/Message/FieldNotFoundException.cs | 18 +- QuickFIXn/Message/Group.cs | 51 +- QuickFIXn/Message/Header.cs | 26 + QuickFIXn/Message/Message.cs | 716 ++++++++----------- QuickFIXn/Message/Trailer.cs | 26 + QuickFIXn/MessageBuilder.cs | 3 +- QuickFIXn/Session.cs | 2 +- QuickFIXn/SocketReader.cs | 2 +- RELEASE_NOTES.md | 21 +- UnitTests/DataDictionaryTests.cs | 57 +- UnitTests/FieldMapTests.cs | 2 +- UnitTests/GroupTests.cs | 2 +- UnitTests/MessageTests.cs | 2 +- UnitTests/MessageToXmlTests.cs | 18 +- 22 files changed, 563 insertions(+), 709 deletions(-) create mode 100644 QuickFIXn/Message/Header.cs create mode 100644 QuickFIXn/Message/Trailer.cs diff --git a/AcceptanceTest/TestBase.cs b/AcceptanceTest/TestBase.cs index 6806c789d..07acedab8 100644 --- a/AcceptanceTest/TestBase.cs +++ b/AcceptanceTest/TestBase.cs @@ -23,7 +23,7 @@ public void Setup() ILogFactory? logFactory = settings.Get().Has("Verbose") && settings.Get().GetBool("Verbose") ? new FileLogFactory(settings) - : null; + : new NullLogFactory(); _acceptor = new ThreadedSocketAcceptor(testApp, storeFactory, settings, logFactory); @@ -43,4 +43,4 @@ protected void RunTest(string definitionPath) using StreamReader sr = new(definitionPath); runner.Run(sr); } -} \ No newline at end of file +} diff --git a/Examples/FixToJson/Examples.FixToJson.csproj b/Examples/FixToJson/Examples.FixToJson.csproj index 2a3da6645..f5a698af4 100644 --- a/Examples/FixToJson/Examples.FixToJson.csproj +++ b/Examples/FixToJson/Examples.FixToJson.csproj @@ -3,11 +3,9 @@ Exe net6.0 - FixToJson - FixToJson + enable Copyright © Connamara Systems, LLC 2022 Connamara Systems, LLC - AnyCPU;x64 false diff --git a/Examples/FixToJson/Program.cs b/Examples/FixToJson/Program.cs index a5540c149..2142ba359 100644 --- a/Examples/FixToJson/Program.cs +++ b/Examples/FixToJson/Program.cs @@ -1,12 +1,15 @@ using System; -using System.Text; using System.IO; -namespace TradeClient +namespace Examples.FixToJson { - class Program + internal static class Program { - static void FixToJson(string fname, bool humanReadableValues, QuickFix.DataDictionary.DataDictionary sessionDataDictionary, QuickFix.DataDictionary.DataDictionary appDataDictionary) + static void FixToJson( + string fname, + bool humanReadableValues, + QuickFix.DataDictionary.DataDictionary? sessionDataDictionary, + QuickFix.DataDictionary.DataDictionary? appDataDictionary) { try { @@ -15,20 +18,19 @@ static void FixToJson(string fname, bool humanReadableValues, QuickFix.DataDicti { QuickFix.IMessageFactory msgFactory = new QuickFix.DefaultMessageFactory(); QuickFix.Message msg = new QuickFix.Message(); - string line = null; string comma = ""; - while ((line = streamReader.ReadLine()) != null) + while (streamReader.ReadLine() is { } line) { line = line.Trim(); msg.FromString(line, false, sessionDataDictionary, appDataDictionary, msgFactory); - Console.WriteLine(comma + msg.ToJSON(humanReadableValues)); + Console.WriteLine(comma + msg.ToJSON(humanReadableValues: humanReadableValues)); comma = ","; } } Console.WriteLine("]\n}"); } - catch (System.Exception e) + catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); @@ -40,27 +42,27 @@ static void Main(string[] args) { if (args.Length < 1 || args.Length > 4) { - System.Console.WriteLine("USAGE"); - System.Console.WriteLine(""); - System.Console.WriteLine(" FixToJson.exe FILE [HUMAN_READABLE_VALUES] [DATA_DICTIONARY]"); - System.Console.WriteLine(""); - System.Console.WriteLine("EXAMPLES"); - System.Console.WriteLine(""); - System.Console.WriteLine(" FixToJson.exe messages.log true ../../spec/fix/FIX50SP2.xml"); - System.Console.WriteLine(" FixToJson.exe messages.log true ../../spec/fix/FIX44.xml"); - System.Console.WriteLine(" FixToJson.exe messages.log false ../../spec/fix/FIX42.xml"); - System.Console.WriteLine(""); - System.Console.WriteLine("NOTE"); - System.Console.WriteLine(""); - System.Console.WriteLine(" Per the FIX JSON Encoding Specification, tags are converted to human-readable form, but values are not."); - System.Console.WriteLine(" Set the HUMAN_READABLE_VALUES argument to TRUE to override the standard behavior."); - System.Environment.Exit(2); + Console.WriteLine("USAGE"); + Console.WriteLine(""); + Console.WriteLine(" FixToJson.exe FILE [HUMAN_READABLE_VALUES] [DATA_DICTIONARY]"); + Console.WriteLine(""); + Console.WriteLine("EXAMPLES"); + Console.WriteLine(""); + Console.WriteLine(" FixToJson.exe messages.log true ../../spec/fix/FIX50SP2.xml"); + Console.WriteLine(" FixToJson.exe messages.log true ../../spec/fix/FIX44.xml"); + Console.WriteLine(" FixToJson.exe messages.log false ../../spec/fix/FIX42.xml"); + Console.WriteLine(""); + Console.WriteLine("NOTE"); + Console.WriteLine(""); + Console.WriteLine(" Per the FIX JSON Encoding Specification, tags are converted to human-readable form, but values are not."); + Console.WriteLine(" Set the HUMAN_READABLE_VALUES argument to TRUE to override the standard behavior."); + Environment.Exit(2); } string fname = args[0]; bool humanReadableValues = false; - QuickFix.DataDictionary.DataDictionary sessionDataDictionary = null; - QuickFix.DataDictionary.DataDictionary appDataDictionary = null; + QuickFix.DataDictionary.DataDictionary? sessionDataDictionary = null; + QuickFix.DataDictionary.DataDictionary? appDataDictionary = null; if (args.Length > 1) { diff --git a/Examples/JsonToFix/Examples.JsonToFix.csproj b/Examples/JsonToFix/Examples.JsonToFix.csproj index b7e62dea4..f5a698af4 100644 --- a/Examples/JsonToFix/Examples.JsonToFix.csproj +++ b/Examples/JsonToFix/Examples.JsonToFix.csproj @@ -3,7 +3,6 @@ Exe net6.0 - enable enable Copyright © Connamara Systems, LLC 2022 Connamara Systems, LLC diff --git a/Examples/JsonToFix/Program.cs b/Examples/JsonToFix/Program.cs index e33c3bee8..38655445f 100644 --- a/Examples/JsonToFix/Program.cs +++ b/Examples/JsonToFix/Program.cs @@ -1,12 +1,11 @@ using System; -using System.Text; -using System.Text.Json; using System.IO; +using System.Text.Json; using QuickFix; -namespace TradeClient +namespace Examples.JsonToFix { - class Program + internal static class Program { static void JsonMsgToFix(string json, QuickFix.DataDictionary.DataDictionary sessionDataDictionary, QuickFix.DataDictionary.DataDictionary appDataDictionary, QuickFix.IMessageFactory msgFactory) { @@ -36,7 +35,7 @@ static void JsonToFix(string fname, QuickFix.DataDictionary.DataDictionary sessi } } } - catch (System.Exception e) + catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); @@ -48,22 +47,22 @@ static void Main(string[] args) { if (args.Length < 1 || args.Length > 2) { - System.Console.WriteLine("USAGE"); - System.Console.WriteLine(""); - System.Console.WriteLine(" FixToJson.exe FILE DATA_DICTIONARY"); - System.Console.WriteLine(""); - System.Console.WriteLine(" The FILE may contain either a single message in FIX JSON Encoding, or an array of messages in a root-level \"messages\" element."); - System.Console.WriteLine(""); - System.Console.WriteLine("EXAMPLES"); - System.Console.WriteLine(""); - System.Console.WriteLine(" JsonToFix.exe messages.json ../../spec/fix/FIX50SP2.xml"); - System.Console.WriteLine(" JsonToFix.exe messages.json ../../spec/fix/FIX44.xml"); - System.Console.WriteLine(" JsonToFix.exe messages.json ../../spec/fix/FIX42.xml"); - System.Console.WriteLine(""); - System.Console.WriteLine("NOTE"); - System.Console.WriteLine(""); - System.Console.WriteLine(" Per the FIX JSON Encoding Specification, tags are converted to human-readable form, but values are not."); - System.Environment.Exit(2); + Console.WriteLine("USAGE"); + Console.WriteLine(""); + Console.WriteLine(" FixToJson.exe FILE DATA_DICTIONARY"); + Console.WriteLine(""); + Console.WriteLine(" The FILE may contain either a single message in FIX JSON Encoding, or an array of messages in a root-level \"messages\" element."); + Console.WriteLine(""); + Console.WriteLine("EXAMPLES"); + Console.WriteLine(""); + Console.WriteLine(" JsonToFix.exe messages.json ../../spec/fix/FIX50SP2.xml"); + Console.WriteLine(" JsonToFix.exe messages.json ../../spec/fix/FIX44.xml"); + Console.WriteLine(" JsonToFix.exe messages.json ../../spec/fix/FIX42.xml"); + Console.WriteLine(""); + Console.WriteLine("NOTE"); + Console.WriteLine(""); + Console.WriteLine(" Per the FIX JSON Encoding Specification, tags are converted to human-readable form, but values are not."); + Environment.Exit(2); } string fname = args[0]; diff --git a/QuickFIXn/DataDictionary/DataDictionary.cs b/QuickFIXn/DataDictionary/DataDictionary.cs index d19bdfe99..2bc866859 100644 --- a/QuickFIXn/DataDictionary/DataDictionary.cs +++ b/QuickFIXn/DataDictionary/DataDictionary.cs @@ -90,8 +90,7 @@ public static void Validate(Message message, DataDictionary sessionDataDict, Dat if (((null != sessionDataDict) && sessionDataDict.CheckFieldsOutOfOrder) || ((null != appDataDict) && appDataDict.CheckFieldsOutOfOrder)) { - int field; - if (!message.HasValidStructure(out field)) + if (!message.HasValidStructure(out var field)) throw new TagOutOfOrder(field); } diff --git a/QuickFIXn/Fields/IField.cs b/QuickFIXn/Fields/IField.cs index 3935a446a..b8eb97d17 100644 --- a/QuickFIXn/Fields/IField.cs +++ b/QuickFIXn/Fields/IField.cs @@ -1,4 +1,5 @@ -namespace QuickFix.Fields +#nullable enable +namespace QuickFix.Fields { /// /// Interface for all field classes @@ -29,4 +30,4 @@ public abstract class IField /// public abstract int getTotal(); } -} \ No newline at end of file +} diff --git a/QuickFIXn/Message/FieldMap.cs b/QuickFIXn/Message/FieldMap.cs index 06c62adfb..256d4e938 100644 --- a/QuickFIXn/Message/FieldMap.cs +++ b/QuickFIXn/Message/FieldMap.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -10,16 +11,27 @@ namespace QuickFix /// /// Field container used by messages, groups, and composites /// - public class FieldMap : IEnumerable> - { + public class FieldMap : IEnumerable> { + private SortedDictionary _fields = new(); + + /// FIXME sorted dict is a hack to get quasi-correct field order + private Dictionary> _groups = new(); + + /// + /// order of field tags as an integer array + /// + public int[] FieldOrder { get; private set; } = Array.Empty(); + + /// + /// Used for validation. Only set during Message parsing. + /// + public List RepeatedTags { get; private set; } = new(); + /// /// Default constructor /// public FieldMap() { - _fields = new SortedDictionary(); // FIXME sorted dict is a hack to get quasi-correct field order - _groups = new Dictionary>(); - this.RepeatedTags = new List(); } /// @@ -29,10 +41,9 @@ public FieldMap() public FieldMap(int[] fieldOrd) : this() { - _fieldOrder = fieldOrd; + FieldOrder = fieldOrd; } - /// /// FIXME this should probably make a deeper copy /// @@ -45,33 +56,15 @@ public FieldMap(FieldMap src) public void CopyStateFrom(FieldMap src) { - this._fieldOrder = src._fieldOrder; + FieldOrder = src.FieldOrder; - this._fields = new SortedDictionary(src._fields); + _fields = new SortedDictionary(src._fields); - this._groups = new Dictionary>(); + _groups = new Dictionary>(); foreach (KeyValuePair> g in src._groups) - this._groups.Add(g.Key, new List(g.Value)); - - this.RepeatedTags = new List(src.RepeatedTags); - } - - /// - /// FieldOrder Property - /// order of field tags as an integer array - /// - public int[] FieldOrder - { - get { return _fieldOrder; } - } + _groups.Add(g.Key, new List(g.Value)); - /// - /// QuickFIX-CPP compat, see FieldOrder property - /// - /// field order integer array - public int[] getFieldOrder() - { - return _fieldOrder; + RepeatedTags = new List(src.RepeatedTags); } /// @@ -88,7 +81,7 @@ public bool RemoveField(int field) /// set field in the fieldmap /// will overwrite field if it exists /// - public void SetField(Fields.IField field) + public void SetField(IField field) { _fields[field.Tag] = field; } @@ -96,7 +89,7 @@ public void SetField(Fields.IField field) /// /// set many fields at the same time /// - public void SetFields(IEnumerable fields) + public void SetFields(IEnumerable fields) { foreach (var field in fields) { @@ -109,8 +102,8 @@ public void SetFields(IEnumerable fields) /// /// /// will overwrite existing field if set to true - /// false if overwrite would be violated, else true - public bool SetField(Fields.IField field, bool overwrite) + /// false if overwrite=true and is denied + public bool SetField(IField field, bool overwrite) { if (_fields.ContainsKey(field.Tag) && !overwrite) return false; @@ -125,7 +118,7 @@ public bool SetField(Fields.IField field, bool overwrite) /// this field's tag is used to extract the value from the message; that value is saved back into this object /// thrown if isn't found /// - public Fields.BooleanField GetField(Fields.BooleanField field) + public BooleanField GetField(BooleanField field) { field.Obj = GetBoolean(field.Tag); return field; @@ -137,7 +130,7 @@ public Fields.BooleanField GetField(Fields.BooleanField field) /// this field's tag is used to extract the value from the message; that value is saved back into this object /// thrown if isn't found /// - public Fields.StringField GetField(Fields.StringField field) + public StringField GetField(StringField field) { field.Obj = GetString(field.Tag); return field; @@ -149,7 +142,7 @@ public Fields.StringField GetField(Fields.StringField field) /// this field's tag is used to extract the value from the message; that value is saved back into this object /// thrown if isn't found /// - public Fields.CharField GetField(Fields.CharField field) + public CharField GetField(CharField field) { field.Obj = GetChar(field.Tag); return field; @@ -161,7 +154,7 @@ public Fields.CharField GetField(Fields.CharField field) /// this field's tag is used to extract the value from the message; that value is saved back into this object /// thrown if isn't found /// - public Fields.IntField GetField(Fields.IntField field) + public IntField GetField(IntField field) { field.Obj = GetInt(field.Tag); return field; @@ -173,7 +166,7 @@ public Fields.IntField GetField(Fields.IntField field) /// this field's tag is used to extract the value from the message; that value is saved back into this object /// thrown if isn't found /// - public Fields.ULongField GetField(Fields.ULongField field) + public ULongField GetField(ULongField field) { field.Obj = GetULong(field.Tag); return field; @@ -185,7 +178,7 @@ public Fields.ULongField GetField(Fields.ULongField field) /// this field's tag is used to extract the value from the message; that value is saved back into this object /// thrown if isn't found /// - public Fields.DecimalField GetField(Fields.DecimalField field) + public DecimalField GetField(DecimalField field) { field.Obj = GetDecimal(field.Tag); return field; @@ -197,7 +190,7 @@ public Fields.DecimalField GetField(Fields.DecimalField field) /// this field's tag is used to extract the value from the message; that value is saved back into this object /// thrown if isn't found /// - public Fields.DateTimeField GetField(Fields.DateTimeField field) + public DateTimeField GetField(DateTimeField field) { field.Obj = GetDateTime(field.Tag); return field; @@ -209,7 +202,7 @@ public Fields.DateTimeField GetField(Fields.DateTimeField field) /// this field's tag is used to extract the value from the message; that value is saved back into this object /// thrown if isn't found /// - public Fields.DateOnlyField GetField(Fields.DateOnlyField field) + public DateOnlyField GetField(DateOnlyField field) { field.Obj = GetDateOnly(field.Tag); return field; @@ -221,7 +214,7 @@ public Fields.DateOnlyField GetField(Fields.DateOnlyField field) /// this field's tag is used to extract the value from the message; that value is saved back into this object /// thrown if isn't found /// - public Fields.TimeOnlyField GetField(Fields.TimeOnlyField field) + public TimeOnlyField GetField(TimeOnlyField field) { field.Obj = GetTimeOnly(field.Tag); return field; @@ -232,7 +225,7 @@ public Fields.TimeOnlyField GetField(Fields.TimeOnlyField field) /// /// Field Object /// true if set - public bool IsSetField(Fields.IField field) + public bool IsSetField(IField field) { return IsSetField(field.Tag); } @@ -268,18 +261,16 @@ internal void AddGroup(Group grp, bool autoIncCounter) // copy, in case user code reuses input object Group group = grp.Clone(); - if (!_groups.ContainsKey(group.Field)) - _groups.Add(group.Field, new List()); - _groups[group.Field].Add(group); + if (!_groups.ContainsKey(group.CounterField)) + _groups.Add(group.CounterField, new List()); + _groups[group.CounterField].Add(group); if (autoIncCounter) { // increment group size - int groupsize = _groups[group.Field].Count; - int counttag = group.Field; - IntField count = null; - - count = new IntField(counttag, groupsize); + int groupsize = _groups[group.CounterField].Count; + int counttag = group.CounterField; + IntField count = new IntField(counttag, groupsize); this.SetField(count, true); } } @@ -304,18 +295,15 @@ public Group GetGroup(int num, int field) return _groups[field][num - 1]; } - //TODO v2: change this to return void /// /// Extracts a repeating-group item into /// /// index of desired group item (index starts at 1, not 0) /// group to populate (group.Field is used by this function to extract the group) - /// A redundant reference to Do not use this. This method will be changed to return void in a future release. - public Group GetGroup(int num, Group group) + public void GetGroup(int num, Group group) { - int tag = group.Field; + int tag = group.CounterField; group.CopyStateFrom(this.GetGroup(num, tag)); - return group; } /// @@ -326,15 +314,11 @@ public Group GetGroup(int num, Group group) /// public int GetInt(int tag) { - if (!_fields.TryGetValue(tag, out IField fld)) - { + if (!_fields.TryGetValue(tag, out IField? fld)) throw new FieldNotFoundException(tag); - } if (fld is FieldBase intField) - { return intField.Obj; - } return IntConverter.Convert(fld.ToString()); } @@ -349,11 +333,10 @@ public ulong GetULong(int tag) { try { - Fields.IField fld = _fields[tag]; + IField fld = _fields[tag]; if (fld.GetType() == typeof(ULongField)) return ((ULongField)fld).Obj; - else - return ULongConverter.Convert(fld.ToString()); + return ULongConverter.Convert(fld.ToString()); } catch (System.Collections.Generic.KeyNotFoundException) { @@ -369,27 +352,16 @@ public ulong GetULong(int tag) /// public DateTime GetDateTime(int tag) { - if (!_fields.TryGetValue(tag, out IField fld)) - { + if (!_fields.TryGetValue(tag, out IField? fld)) throw new FieldNotFoundException(tag); - } - - if (fld is DateOnlyField dateOnlyField) - { - return dateOnlyField.Obj.Date; - } - if (fld is TimeOnlyField timeOnlyField) - { - return new DateTime(1980, 01, 01).Add(timeOnlyField.Obj.TimeOfDay); - } - - if (fld is FieldBase dateTimeField) + return fld switch { - return dateTimeField.Obj; - } - - return DateTimeConverter.ConvertToDateTime(fld.ToString()); + DateOnlyField dateOnlyField => dateOnlyField.Obj.Date, + TimeOnlyField timeOnlyField => new DateTime(1980, 01, 01).Add(timeOnlyField.Obj.TimeOfDay), + FieldBase dateTimeField => dateTimeField.Obj, + _ => DateTimeConverter.ConvertToDateTime(fld.ToString()) + }; } /// @@ -400,15 +372,11 @@ public DateTime GetDateTime(int tag) /// public DateTime GetDateOnly(int tag) { - if (!_fields.TryGetValue(tag, out IField fld)) - { + if (!_fields.TryGetValue(tag, out IField? fld)) throw new FieldNotFoundException(tag); - } if (fld is FieldBase dateTimeField) - { return dateTimeField.Obj.Date; - } return DateTimeConverter.ConvertToDateOnly(fld.ToString()); } @@ -421,15 +389,11 @@ public DateTime GetDateOnly(int tag) /// public DateTime GetTimeOnly(int tag) { - if (!_fields.TryGetValue(tag, out IField fld)) - { + if (!_fields.TryGetValue(tag, out IField? fld)) throw new FieldNotFoundException(tag); - } if (fld is FieldBase dateTimeField) - { return new DateTime(1980, 01, 01).Add(dateTimeField.Obj.TimeOfDay); - } return DateTimeConverter.ConvertToTimeOnly(fld.ToString()); } @@ -442,15 +406,11 @@ public DateTime GetTimeOnly(int tag) /// public bool GetBoolean(int tag) { - if (!_fields.TryGetValue(tag, out IField fld)) - { + if (!_fields.TryGetValue(tag, out IField? fld)) throw new FieldNotFoundException(tag); - } if (fld is FieldBase boolField) - { return boolField.Obj; - } return BoolConverter.Convert(fld.ToString()); } @@ -463,10 +423,8 @@ public bool GetBoolean(int tag) /// public string GetString(int tag) { - if (!_fields.TryGetValue(tag, out IField fld)) - { + if (!_fields.TryGetValue(tag, out IField? fld)) throw new FieldNotFoundException(tag); - } return fld.ToString(); } @@ -479,15 +437,11 @@ public string GetString(int tag) /// public char GetChar(int tag) { - if (!_fields.TryGetValue(tag, out IField fld)) - { + if (!_fields.TryGetValue(tag, out IField? fld)) throw new FieldNotFoundException(tag); - } if (fld is FieldBase charField) - { return charField.Obj; - } return CharConverter.Convert(fld.ToString()); } @@ -500,15 +454,11 @@ public char GetChar(int tag) /// public decimal GetDecimal(int tag) { - if (!_fields.TryGetValue(tag, out IField fld)) - { + if (!_fields.TryGetValue(tag, out IField? fld)) throw new FieldNotFoundException(tag); - } if (fld is FieldBase decimalField) - { return decimalField.Obj; - } return DecimalConverter.Convert(fld.ToString()); } @@ -569,19 +519,19 @@ public virtual void Clear() /// true if no fields or groups have been set public bool IsEmpty() { - return ((_fields.Count == 0) && (_groups.Count == 0)); + return (_fields.Count == 0) && (_groups.Count == 0); } public int CalculateTotal() { int total = 0; - foreach (Fields.IField field in _fields.Values) + foreach (IField field in _fields.Values) { if (field.Tag != Fields.Tags.CheckSum) total += field.getTotal(); } - foreach (Fields.IField field in this.RepeatedTags) + foreach (IField field in this.RepeatedTags) { if (field.Tag != Fields.Tags.CheckSum) total += field.getTotal(); @@ -598,7 +548,7 @@ public int CalculateTotal() public int CalculateLength() { int total = 0; - foreach (Fields.IField field in _fields.Values) + foreach (IField field in _fields.Values) { if (field != null && field.Tag != Tags.BeginString @@ -609,7 +559,7 @@ public int CalculateLength() } } - foreach (Fields.IField field in this.RepeatedTags) + foreach (IField field in this.RepeatedTags) { if (field != null && field.Tag != Tags.BeginString @@ -631,10 +581,7 @@ public int CalculateLength() public virtual string CalculateString() { - if( FieldOrder != null ) - return CalculateString(new StringBuilder(), FieldOrder); - else - return CalculateString(new StringBuilder(), new int[0]); + return CalculateString(new StringBuilder(), FieldOrder); } public virtual string CalculateString(StringBuilder sb, int[] preFields) @@ -655,7 +602,7 @@ public virtual string CalculateString(StringBuilder sb, int[] preFields) } } - foreach (Fields.IField field in _fields.Values) + foreach (IField field in _fields.Values) { if (groupCounterTags.Contains(field.Tag)) continue; @@ -689,16 +636,8 @@ public virtual string CalculateString(StringBuilder sb, int[] preFields) /// /// the counter tag of the group /// - public int GroupCount(int fieldNo) - { - if(_groups.ContainsKey(fieldNo)) - { - return _groups[fieldNo].Count; - } - else - { - return 0; - } + public int GroupCount(int fieldNo) { + return _groups.ContainsKey(fieldNo) ? _groups[fieldNo].Count : 0; } /// @@ -711,19 +650,6 @@ public List GetGroupTags() return new List(_groups.Keys); } - #region Private Members - private SortedDictionary _fields; /// FIXME sorted dict is a hack to get quasi-correct field order - private Dictionary> _groups; - protected int[] _fieldOrder; - #endregion - - #region Properties - /// - /// Used for validation. Only set during Message parsing. - /// - public List RepeatedTags { get; private set; } - #endregion - #region IEnumerable> Members public IEnumerator> GetEnumerator() diff --git a/QuickFIXn/Message/FieldNotFoundException.cs b/QuickFIXn/Message/FieldNotFoundException.cs index f8b81f1d9..da8f86c0a 100644 --- a/QuickFIXn/Message/FieldNotFoundException.cs +++ b/QuickFIXn/Message/FieldNotFoundException.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +#nullable enable +using System; namespace QuickFix { @@ -16,17 +14,21 @@ public class FieldNotFoundException : ApplicationException public FieldNotFoundException() { } public FieldNotFoundException(int tag) - : base("field not found for tag: " + tag.ToString()) { this.Field = tag; } + : base($"field not found for tag: {tag}") + { Field = tag; } public FieldNotFoundException(string message) - : base(message) { Field = -1; } + : base(message) + { Field = -1; } public FieldNotFoundException(string message, System.Exception inner) - : base(message, inner) { Field = -1; } + : base(message, inner) + { Field = -1; } protected FieldNotFoundException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) - : base(info, context) { } + : base(info, context) + { } } } diff --git a/QuickFIXn/Message/Group.cs b/QuickFIXn/Message/Group.cs index 9557d1718..16e71d351 100644 --- a/QuickFIXn/Message/Group.cs +++ b/QuickFIXn/Message/Group.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; +#nullable enable +using System; using System.Text; -using QuickFix.Fields; namespace QuickFix { @@ -14,24 +12,25 @@ public class Group : FieldMap /// /// Create a group with the specified count and delimiter fields. /// - /// tag of the counter field + /// tag of the counter field /// delimiter field's tag (first field in the group) - public Group(int field, int delim) + public Group(int counterField, int delim) + :base(fieldOrd: new[] { delim }) { - Field = field; + CounterField = counterField; Delim = delim; } /// /// Create a group with the specified count and delimiter fields and field ordering. /// - /// tag of the counter field + /// tag of the counter field /// delimiter field's tag (first field in the group) /// the group's member tags in order - public Group(int field, int delim, int[] fieldOrd) + public Group(int counterField, int delim, int[] fieldOrd) :base(fieldOrd) { - Field = field; + CounterField = counterField; Delim = delim; } @@ -42,11 +41,11 @@ public Group(int field, int delim, int[] fieldOrd) public Group(Group src) :base(src) { - Field = src.Field; + CounterField = src.CounterField; Delim = src.Delim; } - virtual public Group Clone() + public virtual Group Clone() { return new Group(this); } @@ -54,37 +53,23 @@ virtual public Group Clone() /// /// Tag of the group's counter field /// - public int Field - { - get { return _field; } - private set { _field = value; } - } + public int CounterField { get; } + + [Obsolete("Use CounterField instead")] + public int Field => CounterField; /// /// Tag of the group's delimiter field (first field in the group) /// - public int Delim - { - get { return _delim; } - private set { _delim = value; } - } + public int Delim { get; } - public override string CalculateString() - { - if (_fieldOrder == null) - return base.CalculateString(new StringBuilder(), new int[] { _delim }); - else - return base.CalculateString(new StringBuilder(), _fieldOrder); // 802 shouldn't be in _fieldOrder + public override string CalculateString() { + return base.CalculateString(new StringBuilder(), FieldOrder ?? new[] { Delim }); } public override string ToString() { return CalculateString(); } - - #region Private Members - private int _field; - private int _delim; - #endregion } } diff --git a/QuickFIXn/Message/Header.cs b/QuickFIXn/Message/Header.cs new file mode 100644 index 000000000..ebfe308fc --- /dev/null +++ b/QuickFIXn/Message/Header.cs @@ -0,0 +1,26 @@ +#nullable enable +using System; +using System.Text; +using QuickFix.Fields; + +namespace QuickFix { + public class Header : FieldMap { + public int[] HEADER_FIELD_ORDER = { Tags.BeginString, Tags.BodyLength, Tags.MsgType }; + + public Header() + : base() { + } + + public Header(Header src) + : base(src) { + } + + public override string CalculateString() { + return base.CalculateString(new StringBuilder(), HEADER_FIELD_ORDER); + } + + public override string CalculateString(StringBuilder sb, int[] preFields) { + return base.CalculateString(sb, HEADER_FIELD_ORDER); + } + } +} diff --git a/QuickFIXn/Message/Message.cs b/QuickFIXn/Message/Message.cs index ed6ac8e3c..5bfc60360 100644 --- a/QuickFIXn/Message/Message.cs +++ b/QuickFIXn/Message/Message.cs @@ -1,121 +1,70 @@ -using System; +#nullable enable +using System; using System.Text; using QuickFix.Fields; using System.Text.RegularExpressions; using System.Text.Json; using System.Collections.Generic; +using QuickFix.DataDictionary; +using DD = QuickFix.DataDictionary.DataDictionary; namespace QuickFix { - public class Header : FieldMap - { - public int[] HEADER_FIELD_ORDER = { Tags.BeginString, Tags.BodyLength, Tags.MsgType }; - - public Header() - : base() - { } - - public Header(Header src) - : base(src) - { } - - public override string CalculateString() - { - return base.CalculateString(new StringBuilder(), HEADER_FIELD_ORDER); - } - - public override string CalculateString(StringBuilder sb, int[] preFields) - { - return base.CalculateString(sb, HEADER_FIELD_ORDER); - } - } - - public class Trailer : FieldMap - { - public int[] TRAILER_FIELD_ORDER = { Tags.SignatureLength, Tags.Signature, Tags.CheckSum }; - - public Trailer() - : base() - { } - - public Trailer(Trailer src) - : base(src) - { } - - public override string CalculateString() - { - return base.CalculateString(new StringBuilder(), TRAILER_FIELD_ORDER); - } - - public override string CalculateString(StringBuilder sb, int[] preFields) - { - return base.CalculateString(sb, TRAILER_FIELD_ORDER); - } - } - /// /// Represents a FIX message /// public class Message : FieldMap { - public const string SOH = "\u0001"; - private int field_ = 0; - private bool validStructure_; + public const char SOH = '\u0001'; - #region Properties - - public Header Header { get; private set; } - public Trailer Trailer { get; private set; } - public DataDictionary.DataDictionary ApplicationDataDictionary { get; private set; } + /// + /// If message is invalid, then this is set to the tag that caused it + /// + private int _invalidField = 0; - #endregion + private bool _isValid = false; - #region Constructors + public Header Header { get; } + public Trailer Trailer { get; } public Message() { - this.Header = new Header(); - this.Trailer = new Trailer(); - this.validStructure_ = true; + Header = new Header(); + Trailer = new Trailer(); + _isValid = true; } - public Message(string msgstr) - : this(msgstr, true) + public Message( + string msgStr, + bool validate = true) + : this(msgStr, null, null, validate) { } - public Message(string msgstr, bool validate) - : this(msgstr, null, null, validate) - { } - - public Message(string msgstr, DataDictionary.DataDictionary dataDictionary, bool validate) - : this() - { - this.ApplicationDataDictionary = dataDictionary; - FromString(msgstr, validate, dataDictionary, dataDictionary, null); - } - - public Message(string msgstr, DataDictionary.DataDictionary sessionDataDictionary, DataDictionary.DataDictionary appDD, bool validate) + /// + /// + /// + /// (if FIX4, transportDict and appDict should be the same) + /// (if FIX4, transportDict and appDict should be the same) + /// + public Message(string msgstr, DD? transportDict, DD? appDict, bool validate) : this() { - this.ApplicationDataDictionary = appDD; - FromStringHeader(msgstr); - if(IsAdmin()) - FromString(msgstr, validate, sessionDataDictionary, sessionDataDictionary, null); + PopulateHeaderFromMessageString(msgstr); + if (IsAdmin()) + FromString(msgstr, validate, transportDict, transportDict, null, false); else - FromString(msgstr, validate, sessionDataDictionary, appDD, null); + FromString(msgstr, validate, transportDict, appDict, null, false); } public Message(Message src) : base(src) { - this.Header = new Header(src.Header); - this.Trailer = new Trailer(src.Trailer); - this.validStructure_ = src.validStructure_; - this.field_= src.field_; + Header = new Header(src.Header); + Trailer = new Trailer(src.Trailer); + _isValid = src._isValid; + _invalidField = src._invalidField; } - #endregion - #region Static Methods public static bool IsAdminMsgType(string msgType) @@ -134,7 +83,7 @@ public static MsgType IdentifyType(string fixstring) return new MsgType(GetMsgType(fixstring)); } - public static StringField ExtractField(string msgstr, ref int pos, DataDictionary.DataDictionary sessionDD, DataDictionary.DataDictionary appDD) + public static StringField ExtractField(string msgstr, ref int pos) { try { @@ -144,56 +93,27 @@ public static StringField ExtractField(string msgstr, ref int pos, DataDictionar int fieldvalend = msgstr.IndexOf((char)1, pos); StringField field = new StringField(tag, msgstr.Substring(pos, fieldvalend - pos)); - /* - TODO data dict stuff - if (((null != sessionDD) && sessionDD.IsDataField(field.Tag)) || ((null != appDD) && appDD.IsDataField(field.Tag))) - { - string fieldLength = ""; - // Assume length field is 1 less - int lenField = field.Tag - 1; - // Special case for Signature which violates above assumption - if (Tags.Signature.Equals(field.Tag)) - lenField = Tags.SignatureLength; - - if ((null != group) && group.isSetField(lenField)) - { - fieldLength = group.GetField(lenField); - soh = equalSign + 1 + atol(fieldLength.c_str()); - } - else if (isSetField(lenField)) - { - fieldLength = getField(lenField); - soh = equalSign + 1 + atol(fieldLength.c_str()); - } - } - */ - pos = fieldvalend + 1; return field; } - catch (System.ArgumentOutOfRangeException e) + catch (ArgumentOutOfRangeException e) { throw new MessageParseError("Error at position (" + pos + ") while parsing msg (" + msgstr + ")", e); } - catch (System.OverflowException e) + catch (OverflowException e) { throw new MessageParseError("Error at position (" + pos + ") while parsing msg (" + msgstr + ")", e); } - catch (System.FormatException e) + catch (FormatException e) { throw new MessageParseError("Error at position (" + pos + ") while parsing msg (" + msgstr + ")", e); } } - public static StringField ExtractField(string msgstr, ref int pos) - { - return ExtractField(msgstr, ref pos, null, null); - } - public static string ExtractBeginString(string msgstr) { int i = 0; - return ExtractField(msgstr, ref i, null, null).Obj; + return ExtractField(msgstr, ref i).Obj; } public static bool IsHeaderField(int tag) @@ -231,11 +151,11 @@ public static bool IsHeaderField(int tag) return false; } } - public static bool IsHeaderField(int tag, DataDictionary.DataDictionary dd) + public static bool IsHeaderField(int tag, DD? dd) { if (IsHeaderField(tag)) return true; - if (null != dd) + if (dd is not null) return dd.IsHeaderField(tag); return false; } @@ -252,16 +172,16 @@ public static bool IsTrailerField(int tag) return false; } } - public static bool IsTrailerField(int tag, DataDictionary.DataDictionary dd) + public static bool IsTrailerField(int tag, DD? dd) { if (IsTrailerField(tag)) return true; - if (null != dd) + if (dd is not null) return dd.IsTrailerField(tag); return false; } - public static string GetFieldOrDefault(FieldMap fields, int tag, string defaultValue) + private static string GetFieldOrDefault(FieldMap fields, int tag, string defaultValue) { if (!fields.IsSetField(tag)) return defaultValue; @@ -276,28 +196,23 @@ public static string GetFieldOrDefault(FieldMap fields, int tag, string defaultV } } - public static SessionID GetReverseSessionID(Message msg) + private static SessionID GetReverseSessionId(Message msg) { return new SessionID( - GetFieldOrDefault(msg.Header, Tags.BeginString, null), - GetFieldOrDefault(msg.Header, Tags.TargetCompID, null), + GetFieldOrDefault(msg.Header, Tags.BeginString, SessionID.NOT_SET), + GetFieldOrDefault(msg.Header, Tags.TargetCompID, SessionID.NOT_SET), GetFieldOrDefault(msg.Header, Tags.TargetSubID, SessionID.NOT_SET), GetFieldOrDefault(msg.Header, Tags.TargetLocationID, SessionID.NOT_SET), - GetFieldOrDefault(msg.Header, Tags.SenderCompID, null), + GetFieldOrDefault(msg.Header, Tags.SenderCompID, SessionID.NOT_SET), GetFieldOrDefault(msg.Header, Tags.SenderSubID, SessionID.NOT_SET), GetFieldOrDefault(msg.Header, Tags.SenderLocationID, SessionID.NOT_SET) ); } - /// - /// FIXME works, but implementation is shady - /// - /// - /// - public static SessionID GetReverseSessionID(string msg) + public static SessionID GetReverseSessionId(string msg) { - Message FIXME = new Message(msg); - return GetReverseSessionID(FIXME); + Message m = new Message(msg); + return GetReverseSessionId(m); } /// @@ -308,7 +223,7 @@ public static SessionID GetReverseSessionID(string msg) /// if 35 tag is missing or malformed public static string GetMsgType(string fixstring) { - Match match = Regex.Match(fixstring, Message.SOH + "35=([^" + Message.SOH + "]*)" + Message.SOH); + Match match = Regex.Match(fixstring, SOH + "35=([^" + SOH + "]*)" + SOH); if (match.Success) return match.Groups[1].Value; @@ -336,59 +251,38 @@ public static ApplVerID GetApplVerID(string beginString) case FixValues.BeginString.FIX50SP2: return new ApplVerID(ApplVerID.FIX50SP2); default: - throw new System.ArgumentException(String.Format("ApplVerID for {0} not supported", beginString)); + throw new ArgumentException($"ApplVerID for {beginString} not supported"); } } #endregion - public bool FromStringHeader(string msgstr) + public override void Clear() + { + _invalidField = 0; + Header.Clear(); + base.Clear(); + Trailer.Clear(); + } + + private void PopulateHeaderFromMessageString(string msgstr) { Clear(); - + int pos = 0; int count = 0; while(pos < msgstr.Length) { StringField f = ExtractField(msgstr, ref pos); - - if((count < 3) && (Header.HEADER_FIELD_ORDER[count++] != f.Tag)) - return false; + + if (count < 3 && Header.HEADER_FIELD_ORDER[count++] != f.Tag) + return; if(IsHeaderField(f.Tag)) - this.Header.SetField(f, false); + Header.SetField(f, false); else break; } - return true; - } - - /// - /// Creates a Message from a FIX string - /// - /// - /// - /// - /// - public void FromString(string msgstr, bool validate, DataDictionary.DataDictionary sessionDD, DataDictionary.DataDictionary appDD) - { - this.ApplicationDataDictionary = appDD; - FromString(msgstr, validate, sessionDD, appDD, null); - } - - /// - /// Create a Message from a FIX string - /// - /// - /// - /// - /// - /// If null, any groups will be constructed as generic Group objects - public void FromString(string msgstr, bool validate, - DataDictionary.DataDictionary sessionDD, DataDictionary.DataDictionary appDD, IMessageFactory msgFactory) - { - this.ApplicationDataDictionary = appDD; - FromString(msgstr, validate, sessionDD, appDD, msgFactory, false); } /// @@ -396,90 +290,91 @@ public void FromString(string msgstr, bool validate, /// /// /// - /// - /// + /// (if FIX4, transportDict and appDict should be the same) + /// (if FIX4, transportDict and appDict will be the same) /// If null, any groups will be constructed as generic Group objects /// (default false) if true, ignores all non-header non-trailer fields. /// Intended for callers that only need rejection-related information from the header. /// - public void FromString(string msgstr, bool validate, - DataDictionary.DataDictionary sessionDD, DataDictionary.DataDictionary appDD, IMessageFactory msgFactory, - bool ignoreBody) + public void FromString( + string msgstr, + bool validate, + DD? transportDict, + DD? appDict, + IMessageFactory? msgFactory = null, + bool ignoreBody = false) { - this.ApplicationDataDictionary = appDD; Clear(); - string msgType = ""; bool expectingHeader = true; bool expectingBody = true; int count = 0; int pos = 0; - DataDictionary.IFieldMapSpec msgMap = null; + IFieldMapSpec? msgMap = null; while (pos < msgstr.Length) { - StringField f = ExtractField(msgstr, ref pos, sessionDD, appDD); + StringField f = ExtractField(msgstr, ref pos); - if (validate && (count < 3) && (Header.HEADER_FIELD_ORDER[count++] != f.Tag)) + if (validate && count < 3 && Header.HEADER_FIELD_ORDER[count++] != f.Tag) throw new InvalidMessage("Header fields out of order"); - if (IsHeaderField(f.Tag, sessionDD)) + if (IsHeaderField(f.Tag, transportDict)) { if (!expectingHeader) { - if (0 == field_) - field_ = f.Tag; - validStructure_ = false; + if (0 == _invalidField) + _invalidField = f.Tag; + _isValid = false; } if (Tags.MsgType.Equals(f.Tag)) { - msgType = f.Obj; - if (appDD != null) + string msgType = f.Obj; + if (appDict is not null) { - msgMap = appDD.GetMapForMessage(msgType); + msgMap = appDict.GetMapForMessage(msgType); } - } + } - if (!this.Header.SetField(f, false)) - this.Header.RepeatedTags.Add(f); + if (!Header.SetField(f, false)) + Header.RepeatedTags.Add(f); - if ((null != sessionDD) && sessionDD.Header.IsGroup(f.Tag)) + if (transportDict is not null && transportDict.Header.IsGroup(f.Tag)) { - pos = SetGroup(f, msgstr, pos, this.Header, sessionDD.Header.GetGroupSpec(f.Tag), sessionDD, appDD, msgFactory); + pos = SetGroup(f, msgstr, pos, Header, transportDict.Header.GetGroupSpec(f.Tag), msgFactory); } } - else if (IsTrailerField(f.Tag, sessionDD)) + else if (IsTrailerField(f.Tag, transportDict)) { expectingHeader = false; expectingBody = false; - if (!this.Trailer.SetField(f, false)) - this.Trailer.RepeatedTags.Add(f); + if (!Trailer.SetField(f, false)) + Trailer.RepeatedTags.Add(f); - if ((null != sessionDD) && sessionDD.Trailer.IsGroup(f.Tag)) + if (transportDict is not null && transportDict.Trailer.IsGroup(f.Tag)) { - pos = SetGroup(f, msgstr, pos, this.Trailer, sessionDD.Trailer.GetGroup(f.Tag), sessionDD, appDD, msgFactory); + pos = SetGroup(f, msgstr, pos, Trailer, transportDict.Trailer.GetGroup(f.Tag), msgFactory); } } else if (ignoreBody==false) { if (!expectingBody) { - if (0 == field_) - field_ = f.Tag; - validStructure_ = false; + if (0 == _invalidField) + _invalidField = f.Tag; + _isValid = false; } expectingHeader = false; if (!SetField(f, false)) { - this.RepeatedTags.Add(f); + RepeatedTags.Add(f); } - - if((null != msgMap) && (msgMap.IsGroup(f.Tag))) + if(msgMap is not null && msgMap.IsGroup(f.Tag)) { - pos = SetGroup(f, msgstr, pos, this, msgMap.GetGroupSpec(f.Tag), sessionDD, appDD, msgFactory); + pos = SetGroup(f, msgstr, pos, this, msgMap.GetGroupSpec(f.Tag), msgFactory); } } } @@ -496,26 +391,33 @@ public void FromString(string msgstr, bool validate, /// /// /// - /// - /// + /// + /// /// If null, any groups will be constructed as generic Group objects - public void FromJson(string json, bool validate, DataDictionary.DataDictionary sessionDD, DataDictionary.DataDictionary appDD, IMessageFactory msgFactory) + public void FromJson(string json, bool validate, + DD transportDict, + DD appDict, + IMessageFactory? msgFactory) { - this.ApplicationDataDictionary = appDD; Clear(); - using (JsonDocument document = JsonDocument.Parse(json)) - { - string beginString = document.RootElement.GetProperty("Header").GetProperty("BeginString").GetString(); - string msgType = document.RootElement.GetProperty("Header").GetProperty("MsgType").GetString(); - DataDictionary.IFieldMapSpec msgMap = appDD.GetMapForMessage(msgType); - FromJson(document.RootElement.GetProperty("Header"), beginString, msgType, msgMap, msgFactory, sessionDD, this.Header); - FromJson(document.RootElement.GetProperty("Body"), beginString, msgType, msgMap, msgFactory, appDD, this); - FromJson(document.RootElement.GetProperty("Trailer"), beginString, msgType, msgMap, msgFactory, sessionDD, this.Trailer); + using (JsonDocument document = JsonDocument.Parse(json)) { + string? beginString = document.RootElement.GetProperty("Header").GetProperty("BeginString").GetString(); + string? msgType = document.RootElement.GetProperty("Header").GetProperty("MsgType").GetString(); + + if (beginString is null || msgType is null) { + throw new ArgumentException( + $"JSON message has invalid/missing beginString ({beginString}) and/or msgType ({msgType})"); + } + + IFieldMapSpec msgMap = appDict.GetMapForMessage(msgType); + FromJson(document.RootElement.GetProperty("Header"), beginString, msgType, msgMap, msgFactory, transportDict, Header); + FromJson(document.RootElement.GetProperty("Body"), beginString, msgType, msgMap, msgFactory, appDict, this); + FromJson(document.RootElement.GetProperty("Trailer"), beginString, msgType, msgMap, msgFactory, transportDict, Trailer); } - this.Header.SetField(new BodyLength(BodyLength()), true); - this.Trailer.SetField(new CheckSum(Fields.Converters.CheckSumConverter.Convert(CheckSum())), true); + Header.SetField(new BodyLength(BodyLength()), true); + Trailer.SetField(new CheckSum(Fields.Converters.CheckSumConverter.Convert(CheckSum())), true); if (validate) { @@ -523,19 +425,27 @@ public void FromJson(string json, bool validate, DataDictionary.DataDictionary s } } - protected void FromJson(JsonElement jsonElement, string beginString, string msgType, DataDictionary.IFieldMapSpec msgMap, IMessageFactory msgFactory, DataDictionary.DataDictionary dataDict, FieldMap fieldMap) + protected void FromJson(JsonElement jsonElement, + string beginString, + string msgType, + IFieldMapSpec msgMap, + IMessageFactory? msgFactory, + DD dataDict, + FieldMap fieldMap) { foreach (JsonProperty field in jsonElement.EnumerateObject()) { - DataDictionary.DDField ddField; - if (dataDict.FieldsByName.TryGetValue(field.Name.ToString(), out ddField)) + if (dataDict.FieldsByName.TryGetValue(field.Name, out DDField? ddField)) { - if ((null != msgMap) && (msgMap.IsGroup(ddField.Tag)) && (JsonValueKind.Array == field.Value.ValueKind)) + if (msgMap is not null && msgMap.IsGroup(ddField.Tag) && JsonValueKind.Array == field.Value.ValueKind) { foreach (JsonElement jsonGrp in field.Value.EnumerateArray()) { - Group grp = msgFactory.Create(beginString, msgType, ddField.Tag); - FromJson(jsonGrp, beginString, msgType, msgMap.GetGroupSpec(ddField.Tag), msgFactory, dataDict, grp); + IGroupSpec grpSpec = msgMap.GetGroupSpec(ddField.Tag); + + Group grp = msgFactory?.Create(beginString, msgType, ddField.Tag) + ?? new Group(ddField.Tag, grpSpec.Delim); + FromJson(jsonGrp, beginString, msgType, grpSpec, msgFactory, dataDict, grp); fieldMap.AddGroup(grp); } } @@ -548,7 +458,7 @@ protected void FromJson(JsonElement jsonElement, string beginString, string msgT else { // this may be a custom tag given by number instead of name - if (Int32.TryParse(field.Name.ToString(), out int customTagNumber)) + if (int.TryParse(field.Name, out int customTagNumber)) { fieldMap.SetField(new StringField(customTagNumber, field.Value.ToString())); } @@ -563,59 +473,52 @@ protected void FromJson(JsonElement jsonElement, string beginString, string msgT /// full message string /// starting character position of group /// full message as FieldMap - /// group definition structure from dd - /// - /// + /// group definition structure from dd /// if null, then this method will use the generic Group class constructor /// protected int SetGroup( - StringField grpNoFld, string msgstr, int pos, FieldMap fieldMap, DataDictionary.IGroupSpec groupDD, - DataDictionary.DataDictionary sessionDataDictionary, DataDictionary.DataDictionary appDD, IMessageFactory msgFactory) + StringField grpNoFld, string msgstr, int pos, FieldMap fieldMap, IGroupSpec groupSpec, + IMessageFactory? msgFactory) { - int grpEntryDelimiterTag = groupDD.Delim; + int grpEntryDelimiterTag = groupSpec.Delim; int grpPos = pos; - Group grp = null; // the group entry being constructed + Group? grp = null; // the group entry being constructed while (pos < msgstr.Length) { grpPos = pos; - StringField f = ExtractField(msgstr, ref pos, sessionDataDictionary, appDD); + StringField f = ExtractField(msgstr, ref pos); if (f.Tag == grpEntryDelimiterTag) { // This is the start of a group entry. - if (grp != null) + if (grp is not null) { // We were already building an entry, so the delimiter means it's done. fieldMap.AddGroup(grp, false); - grp = null; // prepare for new Group } // Create a new group! - if (msgFactory != null) - grp = msgFactory.Create(Message.ExtractBeginString(msgstr), Message.GetMsgType(msgstr), grpNoFld.Tag); - - //If above failed (shouldn't ever happen), just use a generic Group. - if (grp == null) - grp = new Group(grpNoFld.Tag, grpEntryDelimiterTag); + grp = msgFactory?.Create(ExtractBeginString(msgstr), GetMsgType(msgstr), grpNoFld.Tag) + ?? new Group(grpNoFld.Tag, grpEntryDelimiterTag); } - else if (!groupDD.IsField(f.Tag)) + else if (!groupSpec.IsField(f.Tag)) { // This field is not in the group, thus the repeating group is done. - if (grp != null) + if (grp is not null) { fieldMap.AddGroup(grp, false); } return grpPos; } - else if(groupDD.IsField(f.Tag) && grp != null && grp.IsSetField(f.Tag)) + else if(groupSpec.IsField(f.Tag) && grp is not null && grp.IsSetField(f.Tag)) { // Tag is appearing for the second time within a group element. // Presumably the sender didn't set the delimiter (or their DD has a different delimiter). throw new RepeatedTagWithoutGroupDelimiterTagException(grpNoFld.Tag, f.Tag); } - if (grp == null) + if (grp is null) { // This means we got into the group's fields without finding a delimiter tag. throw new GroupDelimiterTagException(grpNoFld.Tag, grpEntryDelimiterTag); @@ -623,33 +526,37 @@ protected int SetGroup( // f is just a field in our group entry. Add it and iterate again. grp.SetField(f); - if(groupDD.IsGroup(f.Tag)) + if(groupSpec.IsGroup(f.Tag)) { // f is a counter for a nested group. Recurse! - pos = SetGroup(f, msgstr, pos, grp, groupDD.GetGroupSpec(f.Tag), sessionDataDictionary, appDD, msgFactory); + pos = SetGroup(f, msgstr, pos, grp, groupSpec.GetGroupSpec(f.Tag), msgFactory); } } return grpPos; } - public bool HasValidStructure(out int field) - { - field = field_; - return validStructure_; + /// + /// Check if this message was deemed valid. + /// + /// If invalid, then this is set to the field that is the problem + /// + public bool HasValidStructure(out int problemField) { + problemField = _isValid ? 0 : _invalidField; + return _isValid; } public void Validate() { try { - int receivedBodyLength = this.Header.GetInt(Tags.BodyLength); + int receivedBodyLength = Header.GetInt(Tags.BodyLength); if (BodyLength() != receivedBodyLength) - throw new InvalidMessage("Expected BodyLength=" + BodyLength() + ", Received BodyLength=" + receivedBodyLength + ", Message.SeqNum=" + this.Header.GetInt(Tags.MsgSeqNum)); + throw new InvalidMessage("Expected BodyLength=" + BodyLength() + ", Received BodyLength=" + receivedBodyLength + ", Message.SeqNum=" + Header.GetInt(Tags.MsgSeqNum)); - int receivedCheckSum = this.Trailer.GetInt(Tags.CheckSum); + int receivedCheckSum = Trailer.GetInt(Tags.CheckSum); if (CheckSum() != receivedCheckSum) - throw new InvalidMessage("Expected CheckSum=" + CheckSum() + ", Received CheckSum=" + receivedCheckSum + ", Message.SeqNum=" + this.Header.GetInt(Tags.MsgSeqNum)); + throw new InvalidMessage("Expected CheckSum=" + CheckSum() + ", Received CheckSum=" + receivedCheckSum + ", Message.SeqNum=" + Header.GetInt(Tags.MsgSeqNum)); } catch (FieldNotFoundException e) { @@ -664,189 +571,182 @@ public void Validate() public void ReverseRoute(Header header) { // required routing tags - this.Header.RemoveField(Tags.BeginString); - this.Header.RemoveField(Tags.SenderCompID); - this.Header.RemoveField(Tags.SenderSubID); - this.Header.RemoveField(Tags.SenderLocationID); - this.Header.RemoveField(Tags.TargetCompID); - this.Header.RemoveField(Tags.TargetSubID); - this.Header.RemoveField(Tags.TargetLocationID); + Header.RemoveField(Tags.BeginString); + Header.RemoveField(Tags.SenderCompID); + Header.RemoveField(Tags.SenderSubID); + Header.RemoveField(Tags.SenderLocationID); + Header.RemoveField(Tags.TargetCompID); + Header.RemoveField(Tags.TargetSubID); + Header.RemoveField(Tags.TargetLocationID); if (header.IsSetField(Tags.BeginString)) { string beginString = header.GetString(Tags.BeginString); if (beginString.Length > 0) - this.Header.SetField(new BeginString(beginString)); + Header.SetField(new BeginString(beginString)); - this.Header.RemoveField(Tags.OnBehalfOfLocationID); - this.Header.RemoveField(Tags.DeliverToLocationID); + Header.RemoveField(Tags.OnBehalfOfLocationID); + Header.RemoveField(Tags.DeliverToLocationID); if (string.CompareOrdinal(beginString, "FIX.4.1") >= 0) { if (header.IsSetField(Tags.OnBehalfOfLocationID)) { - string onBehalfOfLocationID = header.GetString(Tags.OnBehalfOfLocationID); - if (onBehalfOfLocationID.Length > 0) - this.Header.SetField(new DeliverToLocationID(onBehalfOfLocationID)); + string onBehalfOfLocationId = header.GetString(Tags.OnBehalfOfLocationID); + if (onBehalfOfLocationId.Length > 0) + Header.SetField(new DeliverToLocationID(onBehalfOfLocationId)); } if (header.IsSetField(Tags.DeliverToLocationID)) { - string deliverToLocationID = header.GetString(Tags.DeliverToLocationID); - if (deliverToLocationID.Length > 0) - this.Header.SetField(new OnBehalfOfLocationID(deliverToLocationID)); + string deliverToLocationId = header.GetString(Tags.DeliverToLocationID); + if (deliverToLocationId.Length > 0) + Header.SetField(new OnBehalfOfLocationID(deliverToLocationId)); } } } if (header.IsSetField(Tags.SenderCompID)) { - SenderCompID senderCompID = new SenderCompID(); - header.GetField(senderCompID); - if (senderCompID.Obj.Length > 0) - this.Header.SetField(new TargetCompID(senderCompID.Obj)); + SenderCompID senderCompId = new SenderCompID(); + header.GetField(senderCompId); + if (senderCompId.Obj.Length > 0) + Header.SetField(new TargetCompID(senderCompId.Obj)); } if (header.IsSetField(Tags.SenderSubID)) { - SenderSubID senderSubID = new SenderSubID(); - header.GetField(senderSubID); - if (senderSubID.Obj.Length > 0) - this.Header.SetField(new TargetSubID(senderSubID.Obj)); + SenderSubID senderSubId = new SenderSubID(); + header.GetField(senderSubId); + if (senderSubId.Obj.Length > 0) + Header.SetField(new TargetSubID(senderSubId.Obj)); } if (header.IsSetField(Tags.SenderLocationID)) { - SenderLocationID senderLocationID = new SenderLocationID(); - header.GetField(senderLocationID); - if (senderLocationID.Obj.Length > 0) - this.Header.SetField(new TargetLocationID(senderLocationID.Obj)); + SenderLocationID senderLocationId = new SenderLocationID(); + header.GetField(senderLocationId); + if (senderLocationId.Obj.Length > 0) + Header.SetField(new TargetLocationID(senderLocationId.Obj)); } if (header.IsSetField(Tags.TargetCompID)) { - TargetCompID targetCompID = new TargetCompID(); - header.GetField(targetCompID); - if (targetCompID.Obj.Length > 0) - this.Header.SetField(new SenderCompID(targetCompID.Obj)); + TargetCompID targetCompId = new TargetCompID(); + header.GetField(targetCompId); + if (targetCompId.Obj.Length > 0) + Header.SetField(new SenderCompID(targetCompId.Obj)); } if (header.IsSetField(Tags.TargetSubID)) { - TargetSubID targetSubID = new TargetSubID(); - header.GetField(targetSubID); - if (targetSubID.Obj.Length > 0) - this.Header.SetField(new SenderSubID(targetSubID.Obj)); + TargetSubID targetSubId = new TargetSubID(); + header.GetField(targetSubId); + if (targetSubId.Obj.Length > 0) + Header.SetField(new SenderSubID(targetSubId.Obj)); } if (header.IsSetField(Tags.TargetLocationID)) { - TargetLocationID targetLocationID = new TargetLocationID(); - header.GetField(targetLocationID); - if (targetLocationID.Obj.Length > 0) - this.Header.SetField(new SenderLocationID(targetLocationID.Obj)); + TargetLocationID targetLocationId = new TargetLocationID(); + header.GetField(targetLocationId); + if (targetLocationId.Obj.Length > 0) + Header.SetField(new SenderLocationID(targetLocationId.Obj)); } // optional routing tags - this.Header.RemoveField(Tags.OnBehalfOfCompID); - this.Header.RemoveField(Tags.OnBehalfOfSubID); - this.Header.RemoveField(Tags.DeliverToCompID); - this.Header.RemoveField(Tags.DeliverToSubID); + Header.RemoveField(Tags.OnBehalfOfCompID); + Header.RemoveField(Tags.OnBehalfOfSubID); + Header.RemoveField(Tags.DeliverToCompID); + Header.RemoveField(Tags.DeliverToSubID); if(header.IsSetField(Tags.OnBehalfOfCompID)) { - string onBehalfOfCompID = header.GetString(Tags.OnBehalfOfCompID); - if(onBehalfOfCompID.Length > 0) - this.Header.SetField(new DeliverToCompID(onBehalfOfCompID)); + string onBehalfOfCompId = header.GetString(Tags.OnBehalfOfCompID); + if(onBehalfOfCompId.Length > 0) + Header.SetField(new DeliverToCompID(onBehalfOfCompId)); } if(header.IsSetField(Tags.OnBehalfOfSubID)) { - string onBehalfOfSubID = header.GetString( Tags.OnBehalfOfSubID); - if(onBehalfOfSubID.Length > 0) - this.Header.SetField(new DeliverToSubID(onBehalfOfSubID)); + string onBehalfOfSubId = header.GetString( Tags.OnBehalfOfSubID); + if(onBehalfOfSubId.Length > 0) + Header.SetField(new DeliverToSubID(onBehalfOfSubId)); } if(header.IsSetField(Tags.DeliverToCompID)) { - string deliverToCompID = header.GetString(Tags.DeliverToCompID); - if(deliverToCompID.Length > 0) - this.Header.SetField(new OnBehalfOfCompID(deliverToCompID)); + string deliverToCompId = header.GetString(Tags.DeliverToCompID); + if(deliverToCompId.Length > 0) + Header.SetField(new OnBehalfOfCompID(deliverToCompId)); } if(header.IsSetField(Tags.DeliverToSubID)) { - string deliverToSubID = header.GetString(Tags.DeliverToSubID); - if(deliverToSubID.Length > 0) - this.Header.SetField(new OnBehalfOfSubID(deliverToSubID)); + string deliverToSubId = header.GetString(Tags.DeliverToSubID); + if(deliverToSubId.Length > 0) + Header.SetField(new OnBehalfOfSubID(deliverToSubId)); } } public int CheckSum() { return ( - (this.Header.CalculateTotal() + (Header.CalculateTotal() + CalculateTotal() - + this.Trailer.CalculateTotal()) % 256); + + Trailer.CalculateTotal()) % 256); } public bool IsAdmin() { - return this.Header.IsSetField(Tags.MsgType) && IsAdminMsgType(this.Header.GetString(Tags.MsgType)); + return Header.IsSetField(Tags.MsgType) && IsAdminMsgType(Header.GetString(Tags.MsgType)); } public bool IsApp() { - return this.Header.IsSetField(Tags.MsgType) && !IsAdminMsgType(this.Header.GetString(Tags.MsgType)); + return Header.IsSetField(Tags.MsgType) && !IsAdminMsgType(Header.GetString(Tags.MsgType)); } /// /// FIXME less operator new /// - /// - public void SetSessionID(SessionID sessionID) - { - this.Header.SetField(new BeginString(sessionID.BeginString)); - this.Header.SetField(new SenderCompID(sessionID.SenderCompID)); - if (SessionID.IsSet(sessionID.SenderSubID)) - this.Header.SetField(new SenderSubID(sessionID.SenderSubID)); - if (SessionID.IsSet(sessionID.SenderLocationID)) - this.Header.SetField(new SenderLocationID(sessionID.SenderLocationID)); - this.Header.SetField(new TargetCompID(sessionID.TargetCompID)); - if (SessionID.IsSet(sessionID.TargetSubID)) - this.Header.SetField(new TargetSubID(sessionID.TargetSubID)); - if (SessionID.IsSet(sessionID.TargetLocationID)) - this.Header.SetField(new TargetLocationID(sessionID.TargetLocationID)); + /// + public void SetSessionID(SessionID sessionId) + { + Header.SetField(new BeginString(sessionId.BeginString)); + Header.SetField(new SenderCompID(sessionId.SenderCompID)); + if (SessionID.IsSet(sessionId.SenderSubID)) + Header.SetField(new SenderSubID(sessionId.SenderSubID)); + if (SessionID.IsSet(sessionId.SenderLocationID)) + Header.SetField(new SenderLocationID(sessionId.SenderLocationID)); + Header.SetField(new TargetCompID(sessionId.TargetCompID)); + if (SessionID.IsSet(sessionId.TargetSubID)) + Header.SetField(new TargetSubID(sessionId.TargetSubID)); + if (SessionID.IsSet(sessionId.TargetLocationID)) + Header.SetField(new TargetLocationID(sessionId.TargetLocationID)); } public SessionID GetSessionID(Message m) { - bool senderSubIDSet = m.Header.IsSetField(Tags.SenderSubID); - bool senderLocationIDSet = m.Header.IsSetField(Tags.SenderLocationID); - bool targetSubIDSet = m.Header.IsSetField(Tags.TargetSubID); - bool targetLocationIDSet = m.Header.IsSetField(Tags.TargetLocationID); + bool isSetSenderSubId = m.Header.IsSetField(Tags.SenderSubID); + bool isSetSenderLocationId = m.Header.IsSetField(Tags.SenderLocationID); + bool isSetTargetSubId = m.Header.IsSetField(Tags.TargetSubID); + bool isSetTargetLocationId = m.Header.IsSetField(Tags.TargetLocationID); - if (senderSubIDSet && senderLocationIDSet && targetSubIDSet && targetLocationIDSet) + if (isSetSenderSubId && isSetSenderLocationId && isSetTargetSubId && isSetTargetLocationId) return new SessionID(m.Header.GetString(Tags.BeginString), m.Header.GetString(Tags.SenderCompID), m.Header.GetString(Tags.SenderSubID), m.Header.GetString(Tags.SenderLocationID), m.Header.GetString(Tags.TargetCompID), m.Header.GetString(Tags.TargetSubID), m.Header.GetString(Tags.TargetLocationID)); - else if (senderSubIDSet && targetSubIDSet) + + if (isSetSenderSubId && isSetTargetSubId) return new SessionID(m.Header.GetString(Tags.BeginString), m.Header.GetString(Tags.SenderCompID), m.Header.GetString(Tags.SenderSubID), m.Header.GetString(Tags.TargetCompID), m.Header.GetString(Tags.TargetSubID)); - else - return new SessionID( - m.Header.GetString(Tags.BeginString), - m.Header.GetString(Tags.SenderCompID), - m.Header.GetString(Tags.TargetCompID)); - } - public override void Clear() - { - field_ = 0; - this.Header.Clear(); - base.Clear(); - this.Trailer.Clear(); + return new SessionID( + m.Header.GetString(Tags.BeginString), + m.Header.GetString(Tags.SenderCompID), + m.Header.GetString(Tags.TargetCompID)); } private Object lock_ToString = new Object(); @@ -854,33 +754,32 @@ public override string ToString() { lock (lock_ToString) { - this.Header.SetField(new BodyLength(BodyLength()), true); - this.Trailer.SetField(new CheckSum(Fields.Converters.CheckSumConverter.Convert(CheckSum())), true); + Header.SetField(new BodyLength(BodyLength()), true); + Trailer.SetField(new CheckSum(Fields.Converters.CheckSumConverter.Convert(CheckSum())), true); - return this.Header.CalculateString() + CalculateString() + this.Trailer.CalculateString(); + return Header.CalculateString() + CalculateString() + Trailer.CalculateString(); } } protected int BodyLength() { - return this.Header.CalculateLength() + CalculateLength() + this.Trailer.CalculateLength(); + return Header.CalculateLength() + CalculateLength() + Trailer.CalculateLength(); } - private static string FieldMapToXML(DataDictionary.DataDictionary dd, FieldMap fields, int space) + private static string FieldMapToXML(DD? dd, FieldMap fields, int space) { StringBuilder s = new StringBuilder(); - string name = string.Empty; // fields foreach (var f in fields) { s.Append(""); - s.Append(""); + s.Append("number=\"" + f.Key + "\">"); + s.Append(""); s.Append(""); } // now groups @@ -903,56 +802,54 @@ private static string FieldMapToXML(DataDictionary.DataDictionary dd, FieldMap f /// ToJSON() helper method. /// /// an XML string - private static StringBuilder FieldMapToJSON(StringBuilder sb, DataDictionary.DataDictionary dd, FieldMap fields, bool humanReadableValues) + private static StringBuilder FieldMapToJSON(StringBuilder sb, DD? dd, FieldMap fields, bool humanReadableValues) { IList numInGroupTagList = fields.GetGroupTags(); - IList numInGroupFieldList = new List(); - string valueDescription = ""; + IList numInGroupFieldList = new List(); // Non-Group Fields - foreach (var field in fields) + foreach (var (_, field) in fields) { - if (QuickFix.Fields.CheckSum.TAG == field.Value.Tag) + if (QuickFix.Fields.CheckSum.TAG == field.Tag) continue; // FIX JSON Encoding does not include CheckSum - if (numInGroupTagList.Contains(field.Value.Tag)) + if (numInGroupTagList.Contains(field.Tag)) { - numInGroupFieldList.Add(field.Value); + numInGroupFieldList.Add(field); continue; // Groups will be handled below } - if ((dd != null) && ( dd.FieldsByTag.ContainsKey(field.Value.Tag))) + if (dd is not null && dd.FieldsByTag.ContainsKey(field.Tag)) { - sb.Append("\"" + dd.FieldsByTag[field.Value.Tag].Name + "\":"); - if (humanReadableValues) - { - if (dd.FieldsByTag[field.Value.Tag].EnumDict.TryGetValue(field.Value.ToString(), out valueDescription)) + sb.Append("\"" + dd.FieldsByTag[field.Tag].Name + "\":"); + if (humanReadableValues) { + if (dd.FieldsByTag[field.Tag].EnumDict.TryGetValue(field.ToString(), out var valueDescription)) { sb.Append("\"" + valueDescription + "\","); } else - sb.Append("\"" + field.Value.ToString() + "\","); + sb.Append("\"" + field + "\","); } else { - sb.Append("\"" + field.Value.ToString() + "\","); + sb.Append("\"" + field + "\","); } } else { - sb.Append("\"" + field.Value.Tag.ToString() + "\":"); - sb.Append("\"" + field.Value.ToString() + "\","); + sb.Append("\"" + field.Tag + "\":"); + sb.Append("\"" + field + "\","); } } // Group Fields - foreach(Fields.IField numInGroupField in numInGroupFieldList) + foreach(IField numInGroupField in numInGroupFieldList) { // The name of the NumInGroup field is the key of the JSON list containing the Group items - if ((dd != null) && ( dd.FieldsByTag.ContainsKey(numInGroupField.Tag))) + if (dd is not null && dd.FieldsByTag.ContainsKey(numInGroupField.Tag)) sb.Append("\"" + dd.FieldsByTag[numInGroupField.Tag].Name + "\":["); else - sb.Append("\"" + numInGroupField.Tag.ToString() + "\":["); + sb.Append("\"" + numInGroupField.Tag + "\":["); // Populate the JSON list with the Group items for (int counter = 1; counter <= fields.GroupCount(numInGroupField.Tag); counter++) @@ -963,13 +860,13 @@ private static StringBuilder FieldMapToJSON(StringBuilder sb, DataDictionary.Dat } // Remove trailing comma - if (sb.Length > 0 && sb[sb.Length - 1] == ',') + if (sb.Length > 0 && sb[^1] == ',') sb.Remove(sb.Length - 1, 1); sb.Append("],"); } // Remove trailing comma - if (sb.Length > 0 && sb[sb.Length - 1] == ',') + if (sb.Length > 0 && sb[^1] == ',') sb.Remove(sb.Length - 1, 1); return sb; @@ -979,72 +876,43 @@ private static StringBuilder FieldMapToJSON(StringBuilder sb, DataDictionary.Dat /// Get a representation of the message as an XML string. /// (NOTE: this is just an ad-hoc XML; it is NOT FIXML.) /// + /// if null, then field names cannot and will not be in the output /// an XML string - public string ToXML() - { - return ToXML(this.ApplicationDataDictionary); - } - - /// - /// Get a representation of the message as an XML string. - /// (NOTE: this is just an ad-hoc XML; it is NOT FIXML.) - /// - /// an XML string - public string ToXML(DataDictionary.DataDictionary dd) + public string ToXML(DD? dataDictionary = null) { StringBuilder s = new StringBuilder(); s.Append(""); s.Append("
"); - s.Append(FieldMapToXML(dd, Header, 4)); + s.Append(FieldMapToXML(dataDictionary, Header, 4)); s.Append("
"); s.Append(""); - s.Append(FieldMapToXML(dd, this, 4)); + s.Append(FieldMapToXML(dataDictionary, this, 4)); s.Append(""); s.Append(""); - s.Append(FieldMapToXML(dd, Trailer, 4)); + s.Append(FieldMapToXML(dataDictionary, Trailer, 4)); s.Append(""); s.Append("
"); return s.ToString(); } - /// - /// Get a representation of the message as a string in FIX JSON Encoding. - /// See: https://github.com/FIXTradingCommunity/fix-json-encoding-spec - /// - /// a JSON string - public string ToJSON() - { - return ToJSON(this.ApplicationDataDictionary, false); - } - - - /// - /// Get a representation of the message as a string in FIX JSON Encoding. - /// See: https://github.com/FIXTradingCommunity/fix-json-encoding-spec - /// - /// Per the FIX JSON Encoding spec, tags are converted to human-readable form, but values are not. - /// If you want human-readable values, set humanReadableValues to true. - /// - /// a JSON string - public string ToJSON(bool humanReadableValues) - { - return ToJSON(this.ApplicationDataDictionary, humanReadableValues); - } - /// /// Get a representation of the message as a string in FIX JSON Encoding. /// See: https://github.com/FIXTradingCommunity/fix-json-encoding-spec /// /// Per the FIX JSON Encoding spec, tags are converted to human-readable form, but values are not. - /// If you want human-readable values, set humanReadableValues to true. /// + /// Needed if you want tag names emitted or humanReadableValues to work + /// + /// True will cause enums to be converted to human strings. + /// Will not (and cannot!) work if dataDictionary is null. + /// /// a JSON string - public string ToJSON(DataDictionary.DataDictionary dd, bool humanReadableValues) + public string ToJSON(DD? dataDictionary = null, bool humanReadableValues = false) { StringBuilder sb = new StringBuilder().Append("{").Append("\"Header\":{"); - FieldMapToJSON(sb, dd, Header, humanReadableValues).Append("},\"Body\":{"); - FieldMapToJSON(sb, dd, this, humanReadableValues).Append("},\"Trailer\":{"); - FieldMapToJSON(sb, dd, Trailer, humanReadableValues).Append("}}"); + FieldMapToJSON(sb, dataDictionary, Header, humanReadableValues).Append("},\"Body\":{"); + FieldMapToJSON(sb, dataDictionary, this, humanReadableValues).Append("},\"Trailer\":{"); + FieldMapToJSON(sb, dataDictionary, Trailer, humanReadableValues).Append("}}"); return sb.ToString(); } } diff --git a/QuickFIXn/Message/Trailer.cs b/QuickFIXn/Message/Trailer.cs new file mode 100644 index 000000000..4db07dcea --- /dev/null +++ b/QuickFIXn/Message/Trailer.cs @@ -0,0 +1,26 @@ +#nullable enable +using System; +using System.Text; +using QuickFix.Fields; + +namespace QuickFix { + public class Trailer : FieldMap { + public int[] TRAILER_FIELD_ORDER = { Tags.SignatureLength, Tags.Signature, Tags.CheckSum }; + + public Trailer() + : base() { + } + + public Trailer(Trailer src) + : base(src) { + } + + public override string CalculateString() { + return base.CalculateString(new StringBuilder(), TRAILER_FIELD_ORDER); + } + + public override string CalculateString(StringBuilder sb, int[] preFields) { + return base.CalculateString(sb, TRAILER_FIELD_ORDER); + } + } +} diff --git a/QuickFIXn/MessageBuilder.cs b/QuickFIXn/MessageBuilder.cs index c34446fb6..1441d3e77 100644 --- a/QuickFIXn/MessageBuilder.cs +++ b/QuickFIXn/MessageBuilder.cs @@ -50,7 +50,8 @@ internal Message Build() _validateLengthAndChecksum, _sessionDD, _appDD, - _msgFactory); + _msgFactory, + ignoreBody: false); _message = message; return _message; } diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index bb32efb12..181cd54db 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -780,7 +780,7 @@ protected void NextResendRequest(Message resendReq) foreach (string msgStr in messages) { Message msg = new Message(); - msg.FromString(msgStr, true, this.SessionDataDictionary, this.ApplicationDataDictionary, _msgFactory); + msg.FromString(msgStr, true, this.SessionDataDictionary, this.ApplicationDataDictionary, _msgFactory, ignoreBody: false); msgSeqNum = msg.Header.GetULong(Tags.MsgSeqNum); if ((current != msgSeqNum) && begin == 0) diff --git a/QuickFIXn/SocketReader.cs b/QuickFIXn/SocketReader.cs index 93f822518..aecdcf508 100755 --- a/QuickFIXn/SocketReader.cs +++ b/QuickFIXn/SocketReader.cs @@ -124,7 +124,7 @@ private void OnMessageFound(string msg) { if (null == _qfSession) { - _qfSession = Session.LookupSession(Message.GetReverseSessionID(msg)); + _qfSession = Session.LookupSession(Message.GetReverseSessionId(msg)); if (null == _qfSession) { this.Log("ERROR: Disconnecting; received message for unknown session: " + msg); diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 075fd8a20..d19308e0c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -14,10 +14,27 @@ What's New **Breaking changes** * #768 - span-ify parser (Rob-Hague) - makes a change to QuickFix.Parser interface, which isn't likely to affect users -* #820 - cleanup/nullable-ize files (gbirchmeier) - changed some Session Generate* functions to return void instead of null. - Very low likelihood that any user code will be affected. +* #820 - cleanup/nullable-ize initiator/acceptor files (gbirchmeier) - + changed some Session Generate* functions to return void instead of null, + very low likelihood that any user code will be affected. * #821 - delete dead code: ByteSizeString class & test, do-nothing ConfigTest (gbirchmeier) - ByteSizeString is public, but probably no one uses it +* #822 - cleanup/nullable-ize Message/ classes (gbirchmeier) + * FieldMap: getFieldOrder() deleted. Just call FieldOrder. + * FieldMap: GetGroup(int num, Group group) now returns null instead of redundant Group + * Group: rename Field to CounterField + * Message: changed `SOH`'s type from string to char + * Message: ctor Message(string msgstr, DataDictionary.DataDictionary dataDictionary, bool validate) deleted. + You can use the 2-DD version, though for FIX4 those DDs will be the same. + * Message: ExtractField()'s 2 DD params removed. They were never used. + * Message: static GetFieldOrDefault() changed to private. Unlikely anyone will notice. + * Message: static GetReverseSessionID(Message) changed to private. Unlikely anyone will notice. + (The version with the string param is still public) + * Message: FromStringHeader renamed and made private. Unlikely anyone will notice. + * Message: ToXML() and ToJSON() require DD params for any tag/enum translation. + (Message no longer stores a DD instance var.) + These functions are new, probably not widely used yet. + Function docs are now clear on this behavior. **Non-breaking changes** * #400 - added DDTool, a C#-based codegen, and deleted Ruby-based generator (gbirchmeier) diff --git a/UnitTests/DataDictionaryTests.cs b/UnitTests/DataDictionaryTests.cs index 63b8d394b..40446c5b6 100644 --- a/UnitTests/DataDictionaryTests.cs +++ b/UnitTests/DataDictionaryTests.cs @@ -13,6 +13,8 @@ public class DataDictionaryTests { private QuickFix.IMessageFactory _defaultMsgFactory = new QuickFix.DefaultMessageFactory(); + private const char NUL = Message.SOH; + [Test] public void VersionTest() { @@ -305,12 +307,11 @@ public void CheckGroupCountTest() QuickFix.FIX42.NewOrderSingle n = new QuickFix.FIX42.NewOrderSingle(); - string nul = Message.SOH; - string s = "8=FIX.4.2" + nul + "9=148" + nul + "35=D" + nul + "34=2" + nul + "49=TW" + nul + "52=20111011-15:06:23.103" + nul + "56=ISLD" + nul - + "11=ID" + nul + "21=1" + nul + "40=1" + nul + "54=1" + nul + "38=200.00" + nul + "55=INTC" + nul - + "386=3" + nul + "336=PRE-OPEN" + nul + "336=AFTER-HOURS" + nul - + "60=20111011-15:06:23.103" + nul - + "10=35" + nul; + string s = "8=FIX.4.2" + NUL + "9=148" + NUL + "35=D" + NUL + "34=2" + NUL + "49=TW" + NUL + "52=20111011-15:06:23.103" + NUL + "56=ISLD" + NUL + + "11=ID" + NUL + "21=1" + NUL + "40=1" + NUL + "54=1" + NUL + "38=200.00" + NUL + "55=INTC" + NUL + + "386=3" + NUL + "336=PRE-OPEN" + NUL + "336=AFTER-HOURS" + NUL + + "60=20111011-15:06:23.103" + NUL + + "10=35" + NUL; n.FromString(s, true, dd, dd, _defaultMsgFactory); @@ -333,7 +334,7 @@ public void ValidateWrongType() "11=clordid", "55=sym", "54=1", "60=20110909-09:09:09.999", "40=1", "38=failboat", // should be a decimal "10=64"}; - string msgStr = String.Join(Message.SOH, msgFields) + Message.SOH; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; string msgType = "D"; string beginString = "FIX.4.4"; @@ -351,10 +352,9 @@ public void ValidateWithRepeatingGroupTest() dd.LoadFIXSpec("FIX42"); QuickFix.FIX42.MessageFactory f = new QuickFix.FIX42.MessageFactory(); - string nul = Message.SOH; - string msgStr = "8=FIX.4.2" + nul + "9=87" + nul + "35=B" + nul + "34=3" + nul + "49=CLIENT1" + nul - + "52=20111012-22:15:55.474" + nul + "56=EXECUTOR" + nul + "148=AAAAAAA" + nul - + "33=2" + nul + "58=L1" + nul + "58=L2" + nul + "10=016" + nul; + string msgStr = "8=FIX.4.2" + NUL + "9=87" + NUL + "35=B" + NUL + "34=3" + NUL + "49=CLIENT1" + NUL + + "52=20111012-22:15:55.474" + NUL + "56=EXECUTOR" + NUL + "148=AAAAAAA" + NUL + + "33=2" + NUL + "58=L1" + NUL + "58=L2" + NUL + "10=016" + NUL; QuickFix.Fields.MsgType msgType = Message.IdentifyType(msgStr); string beginString = Message.ExtractBeginString(msgStr); @@ -380,10 +380,10 @@ public void ValidateGroupBeginsGroup() + "1111=mundane|5555=magicfield|6660=1|7770=2|7711=Hoppy|7712=brown|" + "7711=Floppy|7712=white|6661=abracadabra|10=48|"; // note: length and checksum might be garbage - string msgStr = pipedStr.Replace("|", Message.SOH); + string msgStr = pipedStr.Replace('|', Message.SOH); string beginString = Message.ExtractBeginString(msgStr); - Message msg = new Message(msgStr, dd, false); + Message msg = new Message(msgStr, dd, dd, false); // true param means body-only, i.e. don't validate length/checksum dd.Validate(msg, true, beginString, "magic"); @@ -414,7 +414,7 @@ public void ValidateWrongTypeInRepeatingGroup() "146=1", // InstrmtMDReqGrp "55=sym", "10=91"}; - string msgStr = String.Join(Message.SOH, msgFields) + Message.SOH; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; string msgType = "V"; string beginString = "FIX.4.4"; @@ -440,7 +440,7 @@ public void ValidateWrongTypeInNestedRepeatingGroup() "757=nested2partyid", "759=failboat", // supposed to be a int "10=48"}; - string msgStr = String.Join(Message.SOH, msgFields) + Message.SOH; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; string msgType = "J"; string beginString = "FIX.4.4"; @@ -460,7 +460,7 @@ public void ValidateDateAndTimeFields() string[] msgFields = { "8=FIX.4.4", "9=104", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", "55=sym", "268=1", "269=0", "272=20111012", "273=22:15:30.444", "10=19" }; - string msgStr = String.Join(Message.SOH, msgFields) + Message.SOH; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; string msgType = "W"; string beginString = "FIX.4.4"; @@ -490,7 +490,7 @@ public void ValidateDateTime_Invalid() // intentionally invalid SendingTime (52/DateTime) string[] msgFields = { "8=FIX.4.4", "9=91", "35=W", "34=3", "49=sender", "52=20110909", "56=target", "55=sym", "268=1", "269=0", "272=20111012", "273=22:15:30.444", "10=51" }; - string msgStr = String.Join(Message.SOH, msgFields) + Message.SOH; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; string msgType = "W"; string beginString = "FIX.4.4"; @@ -511,7 +511,7 @@ public void ValidateDateOnly_Invalid() // intentionally invalid MDEntryDate (272/DateOnly) string[] msgFields = { "8=FIX.4.4", "9=117", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", "55=sym", "268=1", "269=0", "272=20111012-22:15:30.444", "273=22:15:30.444", "10=175" }; - string msgStr = String.Join(Message.SOH, msgFields) + Message.SOH; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; string msgType = "W"; string beginString = "FIX.4.4"; @@ -532,7 +532,7 @@ public void ValidateTimeOnly_Invalid() // intentionally invalid MDEntryTime (272/TimeOnly) string[] msgFields = { "8=FIX.4.4", "9=113", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", "55=sym", "268=1", "269=0", "272=20111012", "273=20111012-22:15:30.444", "10=200" }; - string msgStr = String.Join(Message.SOH, msgFields) + Message.SOH; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; string msgType = "W"; string beginString = "FIX.4.4"; @@ -563,7 +563,7 @@ public void OptionalComponentRequiredField() string[] msgFields = { "8=FIX.4.4", "9=77", "35=AD", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", "568=tradereqid", "569=0", "10=109" }; - string msgStr = String.Join(Message.SOH, msgFields) + Message.SOH; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; string msgType = "AD"; string beginString = "FIX.4.4"; @@ -583,7 +583,7 @@ public void RequiredComponentRequiredField() string[] msgFields = { "8=FIX.4.4", "9=76", "35=7", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", "2=AdvId", "5=N", "4=B", "53=1", "10=138" }; - string msgStr = String.Join(Message.SOH, msgFields) + Message.SOH; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; string msgType = "7"; string beginString = "FIX.4.4"; @@ -628,13 +628,13 @@ public void ValidateMultipleValueStringType() "55=sym", "268=1", "269=0", "270=123.23", "271=2", "277=A B", "10=213"}; - string msgStr = String.Join( Message.SOH, msgFields ) + Message.SOH; + string msgStr = string.Join( Message.SOH, msgFields ) + Message.SOH; string msgType = "W"; string beginString = "FIX.4.4"; Message message = f.Create( beginString, msgType ); - message.FromString( msgStr, true, dd, dd ); + message.FromString(msgStr, true, dd, dd); dd.Validate( message, beginString, msgType ); } @@ -650,13 +650,13 @@ public void ValidateMultipleValueStringType_Invalid() "55=sym", "268=1", "269=0", "270=123.23", "271=2", "277=A 1", "10=196"}; - string msgStr = String.Join( Message.SOH, msgFields ) + Message.SOH; + string msgStr = string.Join( Message.SOH, msgFields ) + Message.SOH; string msgType = "W"; string beginString = "FIX.4.4"; Message message = f.Create( beginString, msgType ); - message.FromString( msgStr, true, dd, dd ); + message.FromString(msgStr, true, dd, dd); Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); } @@ -670,7 +670,7 @@ public void ValidateTagSpecifiedWithoutAValue() string[] msgFields = {"8=FIX.4.2", "9=70", "35=B", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", "358=", "148=", "33=0", "10=150"}; - string msgStr = String.Join(Message.SOH, msgFields) + Message.SOH; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; string msgType = "B"; string beginString = "FIX.4.2"; @@ -712,10 +712,7 @@ XmlNode MakeNode(string xmlString) doc.LoadXml(xmlString); return doc.DocumentElement; } - else - { - return doc.CreateTextNode(xmlString); - } + return doc.CreateTextNode(xmlString); } [Test] diff --git a/UnitTests/FieldMapTests.cs b/UnitTests/FieldMapTests.cs index 0a875933f..25ba7ea4f 100644 --- a/UnitTests/FieldMapTests.cs +++ b/UnitTests/FieldMapTests.cs @@ -300,7 +300,7 @@ public void SimpleFieldOrderTest() public void GroupDelimTest() { Group g1 = new Group(100, 200); - Assert.AreEqual(100, g1.Field); //counter + Assert.AreEqual(100, g1.CounterField); //counter Assert.AreEqual(200, g1.Delim); g1.SetField(new StringField(200, "delim!")); diff --git a/UnitTests/GroupTests.cs b/UnitTests/GroupTests.cs index 990855a20..419b4d70a 100644 --- a/UnitTests/GroupTests.cs +++ b/UnitTests/GroupTests.cs @@ -53,7 +53,7 @@ public void GroupClone() Assert.AreEqual(linesGroup.Text.Obj, clone.Text.Obj); Assert.AreEqual(linesGroup.EncodedText.Obj, clone.EncodedText.Obj); Assert.AreEqual(linesGroup.Delim, clone.Delim); - Assert.AreEqual(linesGroup.Field, clone.Field); + Assert.AreEqual(linesGroup.CounterField, clone.CounterField); Assert.AreEqual(linesGroup.FieldOrder, clone.FieldOrder); } } diff --git a/UnitTests/MessageTests.cs b/UnitTests/MessageTests.cs index 0be9e15af..a8b5dcfda 100644 --- a/UnitTests/MessageTests.cs +++ b/UnitTests/MessageTests.cs @@ -961,7 +961,7 @@ public void ChecksumIsLastFieldOfTrailer() msg.Trailer.SetField(new Signature("woot")); msg.Trailer.SetField(new SignatureLength(4)); - string foo = msg.ToString().Replace(Message.SOH, "|"); + string foo = msg.ToString().Replace(Message.SOH, '|'); StringAssert.EndsWith("|10=099|", foo); } diff --git a/UnitTests/MessageToXmlTests.cs b/UnitTests/MessageToXmlTests.cs index 365e38be7..7596b5b4d 100644 --- a/UnitTests/MessageToXmlTests.cs +++ b/UnitTests/MessageToXmlTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using NUnit.Framework; using QuickFix; @@ -48,13 +47,16 @@ public void ToXMLWithGroupsTest() "448=TFOLIO:6804469", "447=D", "452=36", "10=152" }; - string msgStr = String.Join(Message.SOH, msgFields) + Message.SOH; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; QuickFix.FIX44.ExecutionReport msg = new QuickFix.FIX44.ExecutionReport(); msg.FromString(msgStr, true, dd, dd, null); // <-- null factory! string expected = @"
"; - Assert.AreEqual(expected, msg.ToXML()); + Assert.AreEqual(expected, msg.ToXML(dataDictionary: dd)); + + // If no DD, then output can't have field names + StringAssert.Contains(@"", msg.ToXML()); } [Test] @@ -81,13 +83,19 @@ public void ToJSONWithGroupsTest() "448=TFOLIO:6804469", "447=D", "452=36", "10=152" }; - string msgStr = String.Join(Message.SOH, msgFields) + Message.SOH; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; QuickFix.FIX44.ExecutionReport msg = new QuickFix.FIX44.ExecutionReport(); msg.FromString(msgStr, true, dd, dd, null); // <-- null factory! string expected = "{\"Header\":{\"BeginString\":\"FIX.4.4\",\"BodyLength\":\"638\",\"MsgSeqNum\":\"360\",\"MsgType\":\"8\",\"SenderCompID\":\"BLPTSOX\",\"SendingTime\":\"20130321-15:21:23\",\"TargetCompID\":\"THINKTSOX\",\"TargetSubID\":\"6804469\",\"DeliverToCompID\":\"ZERO\"},\"Body\":{\"AvgPx\":\"122.255\",\"ClOrdID\":\"61101189\",\"CumQty\":\"1990000\",\"Currency\":\"GBP\",\"ExecID\":\"VCON:20130321:50018:5:12\",\"SecurityIDSource\":\"4\",\"LastPx\":\"122.255\",\"LastQty\":\"1990000\",\"OrderID\":\"116\",\"OrderQty\":\"1990000\",\"OrdStatus\":\"2\",\"SecurityID\":\"GB0032452392\",\"Side\":\"1\",\"Symbol\":\"[N/A]\",\"TransactTime\":\"20130321-15:21:23\",\"SettlDate\":\"20130322\",\"TradeDate\":\"20130321\",\"Issuer\":\"UK TSY 4 1/4% 2036\",\"NetMoney\":\"2436321.85\",\"ExecType\":\"F\",\"LeavesQty\":\"0\",\"NumDaysInterest\":\"15\",\"AccruedInterestAmt\":\"3447.35\",\"OrderQty2\":\"0\",\"SecondaryOrderID\":\"3739:20130321:50018:5\",\"CouponRate\":\"0.0425\",\"Factor\":\"1\",\"Yield\":\"0.0291371041\",\"Concession\":\"0\",\"GrossTradeAmt\":\"2432874.5\",\"PriceType\":\"1\",\"CountryOfIssue\":\"GB\",\"MaturityDate\":\"20360307\",\"NoPartyIDs\":[{\"PartyIDSource\":\"D\",\"PartyID\":\"VCON\",\"PartyRole\":\"1\",\"NoPartySubIDs\":[{\"PartySubID\":\"14\",\"PartySubIDType\":\"4\"}]},{\"PartyIDSource\":\"D\",\"PartyID\":\"TFOLIO:6804469\",\"PartyRole\":\"12\"},{\"PartyIDSource\":\"D\",\"PartyID\":\"TFOLIO\",\"PartyRole\":\"11\"},{\"PartyIDSource\":\"D\",\"PartyID\":\"THINKFOLIO LTD\",\"PartyRole\":\"13\"},{\"PartyIDSource\":\"D\",\"PartyID\":\"SXT\",\"PartyRole\":\"16\"},{\"PartyIDSource\":\"D\",\"PartyID\":\"TFOLIO:6804469\",\"PartyRole\":\"36\"}]},\"Trailer\":{}}"; - Assert.AreEqual(expected, msg.ToJSON()); + Assert.AreEqual(expected, msg.ToJSON(dataDictionary: dd, humanReadableValues: false)); + + // emit enums as human-readable strings + StringAssert.Contains("\"MsgType\":\"EXECUTION_REPORT\"", msg.ToJSON(dataDictionary: dd, humanReadableValues: true)); + + // Without a DD: tags aren't translated, and you don't get enums either + StringAssert.Contains("\"35\":\"8\"", msg.ToJSON(dataDictionary: null, humanReadableValues: true)); } } }