From ea1a45c8ec55e3695683f928685521a12df9b248 Mon Sep 17 00:00:00 2001 From: Morten Nielsen Date: Sun, 19 Jan 2020 16:53:39 -0800 Subject: [PATCH] Adds ability to register custom messages --- src/NmeaParser/Nmea/NmeaMessage.cs | 66 +++++++++++++++---- .../NmeaParser.Tests/NmeaMessages.cs | 31 +++++++++ 2 files changed, 84 insertions(+), 13 deletions(-) diff --git a/src/NmeaParser/Nmea/NmeaMessage.cs b/src/NmeaParser/Nmea/NmeaMessage.cs index a773dec..df9f79f 100644 --- a/src/NmeaParser/Nmea/NmeaMessage.cs +++ b/src/NmeaParser/Nmea/NmeaMessage.cs @@ -62,25 +62,65 @@ namespace NmeaParser.Nmea { messageTypes = new Dictionary(); var typeinfo = typeof(NmeaMessage).GetTypeInfo(); - foreach (var subclass in typeinfo.Assembly.DefinedTypes.Where(t => t.IsSubclassOf(typeof(NmeaMessage)))) + RegisterAssembly(typeinfo.Assembly); + } + + /// + /// Registers messages from a different assembly + /// + /// + /// The custom message MUST have a constructor taking a string as first parameter (message type name) and a string[] (message parts) as the second. + /// In addition the class must have the defind on the class. + /// + /// The assembly to load custom message types from + /// Set to true if you want to replace already registered type. Otherwise this method will throw. + /// Number of message types found. + public static int RegisterAssembly(Assembly assembly, bool replace = false) + { + int count = 0; + foreach (var subclass in assembly.DefinedTypes.Where(t => t.IsSubclassOf(typeof(NmeaMessage)) && !t.IsAbstract)) { var attr = subclass.GetCustomAttribute(false); if (attr != null) { - if (!subclass.IsAbstract) - { - foreach (var c in subclass.DeclaredConstructors) - { - var pinfo = c.GetParameters(); - if (pinfo.Length == 2 && pinfo[0].ParameterType == typeof(string) && pinfo[1].ParameterType == typeof(string[])) - { - messageTypes.Add(attr.NmeaType, c); - break; - } - } - } + RegisterNmeaMessage(subclass, attr.NmeaType, replace); + count++; } } + return count; + } + + /// + /// Registers a specific NMEA Message type + /// + /// TypeInfo for the class being registered + /// The 5-character NMEA Type name (eg GPGLL). If null, it'll expect the to be declared on the class. + /// Set to true if you want to replace already registered type. Otherwise this method will throw. + public static void RegisterNmeaMessage(TypeInfo typeInfo, string nmeaType = "", bool replace = false) + { + if (string.IsNullOrEmpty(nmeaType)) + { + var attr = typeInfo.GetCustomAttribute(false); + if (attr == null) + throw new ArgumentException("Message does not have a NmeaMessageTypeAttribute and no type name was specified."); + nmeaType = attr.NmeaType; + if (string.IsNullOrEmpty(nmeaType)) + { + throw new ArgumentException("No NmeaType declared on the NmeaMessageTypeAttribute."); + } + } + foreach (var c in typeInfo.DeclaredConstructors) + { + var pinfo = c.GetParameters(); + if (pinfo.Length == 2 && pinfo[0].ParameterType == typeof(string) && pinfo[1].ParameterType == typeof(string[])) + { + if (!replace && messageTypes.ContainsKey(nmeaType)) + throw new InvalidOperationException($"Message type {nmeaType} declared in {typeInfo.FullName} is already registered by {messageTypes[nmeaType].DeclaringType.FullName}"); + messageTypes[nmeaType] = c; + return; + } + } + throw new ArgumentException("Type does not have a constructor with parameters (string,string[])"); } /// diff --git a/src/UnitTests/NmeaParser.Tests/NmeaMessages.cs b/src/UnitTests/NmeaParser.Tests/NmeaMessages.cs index 97e9ede..744e875 100644 --- a/src/UnitTests/NmeaParser.Tests/NmeaMessages.cs +++ b/src/UnitTests/NmeaParser.Tests/NmeaMessages.cs @@ -810,5 +810,36 @@ namespace NmeaParser.Tests var zda = (Zda)msg; Assert.AreEqual(new DateTimeOffset(2015, 09, 21, 22, 56, 27, 00, TimeSpan.Zero), zda.FixDateTime); } + + [TestMethod] + public void TestCustomMessageRegistration() + { + int count = NmeaMessage.RegisterAssembly(typeof(CustomMessage).Assembly, true); + Assert.AreEqual(1, count); + var input = "$PTEST,TEST*7C"; + var msg = NmeaMessage.Parse(input); + Assert.IsInstanceOfType(msg, typeof(CustomMessage)); + var cmsg = (CustomMessage)msg; + Assert.AreEqual("TEST", cmsg.Value); + } + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public void TestCustomMessageDuplicateRegistrationFailure() + { + int count = NmeaMessage.RegisterAssembly(typeof(CustomMessage).Assembly, true); + Assert.AreEqual(1, count); + count = NmeaMessage.RegisterAssembly(typeof(CustomMessage).Assembly, false); // This will throw + } + + [Nmea.NmeaMessageType("PTEST")] + private class CustomMessage : NmeaMessage + { + public CustomMessage(string type, string[] parameters) : base(type, parameters) + { + Value = parameters[0]; + } + public string Value { get; } + } } }