123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852 |
- using Edge.Core.Parser.BinaryParser.Attributes;
- using Edge.Core.Parser.BinaryParser.MessageEntity;
- using Edge.Core.Parser.BinaryParser.Util;
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.ComponentModel.DataAnnotations;
- using System.Linq;
- using System.Reflection;
- using System.Text;
- using System.Threading.Tasks;
- namespace Edge.Core.Parser.BinaryParser
- {
- public abstract class ParserBase : IMessageParser<byte[], MessageTemplateBase>
- {
- protected static NLog.Logger logger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("Communicator");
- private int debug_ConvertToObject_depth = 0;
- private IMessageTemplateLookup templateLookup;
- /// <summary>
- /// ongoing processing raw data array, make 2 of this array is for thread safe
- /// </summary>
- private byte[] processingRawDataUsedInSerialize = null;
- /// <summary>
- /// ongoing processing raw data array, make 2 of this array is for thread safe
- /// </summary>
- private byte[] processingRawDataUsedInDeserialize = null;
- private int positiveIndexAccu = 0;
- /// <summary>
- /// Fired on when bytes incoming and will start to convert to a object, but not started yet.
- /// </summary>
- public event EventHandler<ParsingEventArg<byte[], MessageTemplateBase>> Deserializing;
- /// <summary>
- /// Fired on when bytes incoming and started to convert to a object, and finished already.
- /// </summary>
- public event EventHandler<DeserializeEventArg<byte[], MessageTemplateBase>> Deserialized;
- public event EventHandler<ParsingEventArg<MessageTemplateBase, byte[]>> Serializing;
- public event EventHandler<ParsingEventArg<MessageTemplateBase, byte[]>> Serialized;
- /// <summary>
- /// Fired on a Format Attibute marked field will starting to convert to bytes, but not started yet.
- /// string in ParsingEventArg is the field name, byte[] should always null here since not started.
- /// </summary>
- public event EventHandler<ParsingEventArg<string, byte[]>> FieldSerializing;
- /// <summary>
- /// Fired on a Format Attibute marked field started to convert to bytes, and finished already.
- /// string in ParsingEventArg is the field name, byte[] should have values for this already serialized field.
- /// </summary>
- public event EventHandler<ParsingEventArg<string, byte[]>> FieldSerialized;
- protected ParserBase(IMessageTemplateLookup templateLookup)
- {
- this.templateLookup = templateLookup;
- }
- protected ParserBase() : this(null)
- {
- }
- /// <summary>
- /// There's a build-in check in Deserialize process, if all fields in a template had been filled, but still
- /// some extra bytes left, an exception will throw, this must be the error in raw bytes.
- /// But you can.
- /// </summary>
- public bool IgnoreExtraTailBytesInDeserialize
- {
- get; set;
- }
- /// <summary>
- /// Deserialize a byte[] into a Message entity, the `TemplateLookup` must specified firstly via constructor.
- /// </summary>
- /// <param name="rawData"></param>
- /// <returns></returns>
- public virtual MessageTemplateBase Deserialize(byte[] rawData)
- {
- //if (rawData.Length < 5)
- //{
- // return default(MessageTemplateBase);
- //}
- if (this.templateLookup == null) throw new ArgumentNullException("Must specify a 'TemplateLookup' to start Deserialize");
- return this.Deserialize(rawData, templateLookup.GetMessageTemplateByRawBytes(rawData));
- }
- public virtual MessageTemplateBase Deserialize(byte[] rawData, MessageTemplateBase withTemplate)
- {
- this.debug_ConvertToObject_depth = 0;
- var debug = rawData.ToHexLogString();
- var safeEvent = this.Deserializing;
- safeEvent?.Invoke(this, new ParsingEventArg<byte[], MessageTemplateBase>() { From = rawData, To = default(MessageTemplateBase) });
- //MessageTemplateBase template = templateLookup.GetMessageTemplateByRawBytes(rawData);
- this.processingRawDataUsedInDeserialize = rawData;
- this.ConvertToObject(withTemplate, this.processingRawDataUsedInDeserialize);
- if (this.processingRawDataUsedInDeserialize.Length > 0)
- {
- if (!IgnoreExtraTailBytesInDeserialize)
- throw new FormatException("Unexpected Element Deserialize error occured(additional tail existed)");
- }
- #region if message level custom validation defined? then execute it
- Exception customValidationException;
- if ((customValidationException = withTemplate.Validate()) != null)
- {
- throw customValidationException;
- }
- #endregion
- var safeDeserializedEvent = this.Deserialized;
- safeDeserializedEvent?.Invoke(this, new DeserializeEventArg<byte[], MessageTemplateBase>() { From = rawData, Extra = this.processingRawDataUsedInDeserialize, To = withTemplate });
- return withTemplate;
- }
- public virtual byte[] Serialize(MessageTemplateBase message)
- {
- #region if message level custom validation defined? then execute it
- Exception customValidationException;
- if ((customValidationException = message?.Validate()) != null)
- {
- throw customValidationException;
- }
- #endregion
- var safeEvent = this.Serializing;
- safeEvent?.Invoke(this, new ParsingEventArg<MessageTemplateBase, byte[]>() { From = message, To = null });
- this.processingRawDataUsedInSerialize = new byte[0];
- this.positiveIndexAccu = 0;
- var content = this.ConvertToBytes(message);
- safeEvent = this.Serialized;
- safeEvent?.Invoke(this, new ParsingEventArg<MessageTemplateBase, byte[]>() { From = message, To = content });
- return content;
- }
- private void ConvertToObject(object elementInstance, byte[] rawData)
- {
- //if (++this.debug_ConvertToObject_depth >= 6) logger.Info("debug_ConvertToObject_depth is " + this.debug_ConvertToObject_depth + ": 0x" + rawData.ToHexLogString());
- if (rawData.Length == 0 || elementInstance == null)
- {
- return;
- }
- else
- {
- // Only processing Format related Attribute marked properties, and order by its Index
- var targetPropertyList = elementInstance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
- .Where(p => p.GetCustomAttributes(typeof(AttributeBase), true).Length > 0)
- .OrderBy(info => ((AttributeBase)(info.GetCustomAttributes(typeof(AttributeBase), true)[0])).Index).ToList();
- foreach (var propertyInfo in targetPropertyList)
- {
- // normal plain Property
- if (propertyInfo.GetCustomAttributes(typeof(EnumerableFormatAttribute), true).Length == 0)
- {
- int currentElementLength = -1;
- var format =
- (FormatAttribute)propertyInfo.GetCustomAttributes(typeof(FormatAttribute), true)[0];
- if (!string.IsNullOrEmpty(format.LengthOrCountLink))
- {
- currentElementLength = int.Parse(
- elementInstance.GetType().GetProperty(format.LengthOrCountLink).GetValue(elementInstance, null).ToString());
- if (!string.IsNullOrEmpty(format.LengthOrCountLinkExpression))
- {
- currentElementLength += ParseLengthOrCountLinkExpression(format.LengthOrCountLinkExpression);
- }
- }
- else
- {
- currentElementLength = format.FixedLengthOrCount;
- }
- // check if the remaining raw data bytes are enough to fill the current property
- if (this.processingRawDataUsedInDeserialize.Count() < currentElementLength)
- {
- throw new ArgumentException(
- string.Format("ConvertToObject Error: The remaining raw data bytes are insufficient to fill the current property '{0}'",
- propertyInfo.Name));
- }
- this.FillPropertyData(format.EncodingType, propertyInfo, elementInstance, this.processingRawDataUsedInDeserialize.Take(currentElementLength).ToArray());
- #region validate the range for Range Attribute marked field.
- // Validate the range, once failed, throw a exception
- var rangeValidator = propertyInfo.GetCustomAttributes(typeof(RangeAttribute), true).Length > 0
- ? propertyInfo.GetCustomAttributes(typeof(RangeAttribute), true)[0] : null;
- if (rangeValidator != null
- && !((RangeAttribute)rangeValidator).IsValid(propertyInfo.GetValue(elementInstance, null)))
- {
- throw new ArgumentException(((RangeAttribute)rangeValidator).ErrorMessage);
- }
- #endregion
- this.processingRawDataUsedInDeserialize = this.processingRawDataUsedInDeserialize.Skip(currentElementLength).ToArray();
- }
- // the IList Property
- else
- {
- bool isCascadedList = false;
- var listFormat =
- (EnumerableFormatAttribute)propertyInfo.GetCustomAttributes(typeof(EnumerableFormatAttribute), true)[0];
- int? listCount = null;
- if (!string.IsNullOrEmpty(listFormat.LengthOrCountLink) && listFormat.LengthOrCountLink.ToLower() == "%cascade")
- {
- // never know the end of the data, it's a flow, so set a big value.
- listCount = this.processingRawDataUsedInDeserialize.Length;
- listCount += ParseLengthOrCountLinkExpression(listFormat.LengthOrCountLinkExpression);
- isCascadedList = true;
- }
- else if (!string.IsNullOrEmpty(listFormat.LengthOrCountLink))
- {
- var linkPropertyValue = elementInstance.GetType().GetProperty(listFormat.LengthOrCountLink).GetValue(elementInstance, null);
- if (linkPropertyValue is Enum)
- {
- listCount = (int)linkPropertyValue;
- }
- else
- {
- listCount = int.Parse(linkPropertyValue.ToString());
- }
- if (!string.IsNullOrEmpty(listFormat.LengthOrCountLinkExpression))
- {
- listCount += ParseLengthOrCountLinkExpression(listFormat.LengthOrCountLinkExpression);
- }
- }
- else if (listFormat.FixedLengthOrCount > 0)
- {
- listCount = listFormat.FixedLengthOrCount;
- }
- else
- {
- throw new FormatException("Can't resolve the count number for IList property: " + propertyInfo.Name);
- }
- IList list = null;
- // since it's CascadedList, then probably there's no data anymore, it's a empty list, no need to do any parsing.
- if (this.processingRawDataUsedInDeserialize.Count() == 0 && isCascadedList)
- {
- }
- else
- {
- list = (IList)Activator.CreateInstance(propertyInfo.PropertyType);
- var genericArg = list.GetType().GetGenericArguments()[0];
- for (int i = 0; i < listCount; i++)
- {
- if (genericArg.IsPrimitive)
- {
- var subPrimitiveInstance = Activator.CreateInstance(genericArg);
- // assume primitive always take 1 byte, since we only use int and byte. need refine future.
- subPrimitiveInstance = this.processingRawDataUsedInDeserialize.Take(listFormat.ElementLength).ToArray().ToInt32();
- if (genericArg.IsAssignableFrom(typeof(Byte)))
- {
- list.Add(Convert.ToByte(subPrimitiveInstance));
- }
- else if (genericArg.IsAssignableFrom(typeof(int)))
- {
- list.Add(subPrimitiveInstance);
- }
- else
- {
- throw new ArgumentException("Now Only support primitive types of 'byte' and 'int'");
- }
- this.processingRawDataUsedInDeserialize = this.processingRawDataUsedInDeserialize.Skip(listFormat.ElementLength).ToArray();
- }
- else
- {
- var subElementInstance = Activator.CreateInstance(genericArg);
- list.Add(subElementInstance);
- this.ConvertToObject(subElementInstance, this.processingRawDataUsedInDeserialize);
- }
- // no data need to be processing, break it since we might set a big listCount earlier for cascade structure.
- // or you'll see a big List<T>
- if (this.processingRawDataUsedInDeserialize.Length == 0)
- {
- break;
- }
- }
- }
- propertyInfo.SetValue(elementInstance, list, null);
- }
- }
- }
- }
- /// <summary>
- /// parse the expression and return the operator value.
- /// </summary>
- /// <param name="expression"></param>
- /// <returns></returns>
- protected virtual int ParseLengthOrCountLinkExpression(string expression)
- {
- if (expression != null
- && (expression.Contains('+') || expression.Contains('-')))
- {
- var pureOperatorAndNumber = expression.Trim().Replace(" ", "");
- if (pureOperatorAndNumber.Contains('+'))
- {
- return int.Parse(pureOperatorAndNumber.Substring(1));
- }
- else if (pureOperatorAndNumber.Contains('-'))
- {
- return -int.Parse(pureOperatorAndNumber.Substring(1));
- }
- }
- return 0;
- }
- /// <summary>
- /// Set the value to a property in a specific object based on the encoding type associated with the property.
- /// </summary>
- /// <param name="encodingType"></param>
- /// <param name="propertyInfo"></param>
- /// <param name="elementInstance"></param>
- /// <param name="data"></param>
- private void FillPropertyData(EncodingType encodingType, PropertyInfo propertyInfo, object elementInstance, byte[] data)
- {
- switch (encodingType)
- {
- case EncodingType.ASCII:
- case EncodingType.ASCII_PadLeftWithZero:
- propertyInfo.SetValue(elementInstance, Encoding.ASCII.GetString(data), null);
- break;
- case EncodingType.ASCIIInt:
- propertyInfo.SetValue(elementInstance, int.Parse(Encoding.ASCII.GetString(data)), null);
- break;
- case EncodingType.ASCIILong:
- propertyInfo.SetValue(elementInstance, long.Parse(Encoding.ASCII.GetString(data)), null);
- break;
- case EncodingType.BCD:
- var tempBytes = data;
- var newValue = tempBytes.Length == 1 ? tempBytes[0].GetBCD() : tempBytes.GetBCD();
- if (propertyInfo.PropertyType.FullName == "System.Byte")
- {
- propertyInfo.SetValue(elementInstance, (byte)newValue, null);
- }
- else
- {
- propertyInfo.SetValue(elementInstance, newValue, null);
- }
- break;
- case EncodingType.BcdString:
- tempBytes = data;
- //if (propertyInfo.PropertyType.BaseType.FullName == "System.Enum") {
- // if (!Enum.IsDefined(propertyInfo.PropertyType, tempBytes.GetBCD()))
- // throw new ArgumentException("The given value is not a valid " + propertyInfo.PropertyType + " element.");
- //}
- var fieldValue = tempBytes.Length == 1 ? tempBytes[0].GetBCD().ToString() : tempBytes.GetBCDString();
- if (propertyInfo.PropertyType.IsAssignableFrom(typeof(long)))
- {
- propertyInfo.SetValue(elementInstance, long.Parse(fieldValue)
- , null);
- }
- else if (propertyInfo.PropertyType.IsAssignableFrom(typeof(DateTime)))
- {
- propertyInfo.SetValue(elementInstance, DateTime.ParseExact(fieldValue, "yyyyMMddHHmmss", System.Globalization.CultureInfo.InvariantCulture));
- }
- else if (propertyInfo.PropertyType.IsAssignableFrom(typeof(decimal)))
- {
- propertyInfo.SetValue(elementInstance, decimal.Parse(fieldValue)
- , null);
- }
- else
- {
- propertyInfo.SetValue(elementInstance, fieldValue
- , null);
- }
- break;
- case EncodingType.BIN:
- if (propertyInfo.PropertyType.IsAssignableFrom(typeof(Enum)))
- {
- var o = Enum.ToObject(propertyInfo.PropertyType, data.ToInt32());
- propertyInfo.SetValue(elementInstance, o, null);
- }
- else if (propertyInfo.PropertyType == typeof(Byte))
- {
- try
- {
- propertyInfo.SetValue(elementInstance, (byte)(data.ToInt32()), null);
- }
- catch (Exception e)
- {
- }
- }
- // in case the Property is a Nullable enum type.
- else if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
- {
- if (propertyInfo.PropertyType.GetGenericArguments()[0].IsEnum)
- {
- var o = Enum.ToObject(propertyInfo.PropertyType.GetGenericArguments()[0], data.ToInt32());
- propertyInfo.SetValue(elementInstance, o, null);
- }
- else
- {
- var value = Convert.ChangeType(data.ToInt32(), propertyInfo.PropertyType);
- propertyInfo.SetValue(elementInstance, value, null);
- }
- }
- else if (propertyInfo.PropertyType == typeof(ushort))
- {
- try
- {
- propertyInfo.SetValue(elementInstance, Convert.ToUInt16(data.ToInt32()));
- }
- catch (Exception ex)
- {
- throw;
- }
- }
- else
- {
- propertyInfo.SetValue(elementInstance, data.ToInt32(), null);
- }
- break;
- case EncodingType.HexString:
- propertyInfo.SetValue(elementInstance, ByteArrayToString(data));
- break;
- case EncodingType.ReverseHexString:
- propertyInfo.SetValue(elementInstance, ByteArrayToString(data.Reverse().ToArray()));
- break;
- }
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="encodingType"></param>
- /// <param name="propertyValue"></param>
- /// <param name="propertyValueFixedLength">if length is avarible bytes, then leave null, the serialized bytes will auto fits the actual value.</param>
- /// <param name="elementInstance"></param>
- /// <returns></returns>
- private byte[] SerializePropertyData(EncodingType encodingType, object propertyValue, int? propertyValueFixedLength, object elementInstance)
- {
- string enumTypeConvertedValue;
- switch (encodingType)
- {
- case EncodingType.ASCII:
- //if (propertyValue is Enum)
- //{
- // var temp = ((int)propertyValue).ToString();
- // enumTypeConvertedValue = temp.ToString().PadLeft(propertyValueFixedLength, '0');
- //}
- //else
- //{
- enumTypeConvertedValue = propertyValue.ToString();
- //}
- if (propertyValueFixedLength.HasValue && enumTypeConvertedValue.Length > propertyValueFixedLength.Value)
- throw new ArgumentOutOfRangeException("ASCII encoded property with value: " + enumTypeConvertedValue
- + " exceed its declared Fixedlength(" + propertyValueFixedLength + "), try shorten the value");
- if (propertyValueFixedLength.HasValue)
- return Encoding.ASCII.GetBytes(enumTypeConvertedValue.PadRight(propertyValueFixedLength.Value, ' '));
- else
- return Encoding.ASCII.GetBytes(enumTypeConvertedValue);
- break;
- case EncodingType.ASCII_PadLeftWithZero:
- enumTypeConvertedValue = propertyValue.ToString();
- if (propertyValueFixedLength.HasValue && enumTypeConvertedValue.Length > propertyValueFixedLength.Value)
- throw new ArgumentOutOfRangeException("ASCII_PadLeftWithZero encoded property with value: " + enumTypeConvertedValue
- + " exceed its declared Fixedlength(" + propertyValueFixedLength + "), try shorten the value");
- if (propertyValueFixedLength.HasValue)
- return Encoding.ASCII.GetBytes(enumTypeConvertedValue.PadLeft(propertyValueFixedLength.Value, '0'));
- else
- return Encoding.ASCII.GetBytes(enumTypeConvertedValue);
- break;
- case EncodingType.ASCIIInt:
- if (propertyValue is Enum)
- {
- var temp = ((int)propertyValue).ToString();
- enumTypeConvertedValue = temp.ToString().PadLeft(propertyValueFixedLength.Value, '0');
- }
- else
- {
- enumTypeConvertedValue = propertyValue.ToString().PadLeft(propertyValueFixedLength.Value, '0'); ;
- }
- return Encoding.ASCII.GetBytes(enumTypeConvertedValue);
- break;
- case EncodingType.ASCIILong:
- if (propertyValue is Enum)
- {
- var temp = ((long)propertyValue).ToString();
- enumTypeConvertedValue = temp.ToString().PadLeft(propertyValueFixedLength.Value, '0');
- }
- else
- {
- enumTypeConvertedValue = propertyValue.ToString().PadLeft(propertyValueFixedLength.Value, '0'); ;
- }
- return Encoding.ASCII.GetBytes(enumTypeConvertedValue);
- break;
- case EncodingType.BCD:
- if (propertyValue is Enum)
- {
- enumTypeConvertedValue = ((int)propertyValue).ToString();
- }
- else
- {
- enumTypeConvertedValue = propertyValue.ToString();
- }
- return long.Parse(enumTypeConvertedValue).GetBCDBytes(propertyValueFixedLength.Value);
- break;
- case EncodingType.BcdString:
- if (propertyValue is Enum)
- {
- enumTypeConvertedValue = ((int)propertyValue).ToString();
- }
- else
- {
- enumTypeConvertedValue = propertyValue.ToString();
- }
- return System.Numerics.BigInteger.Parse(enumTypeConvertedValue).GetBCDBytes(propertyValueFixedLength.Value);
- break;
- case EncodingType.BIN:
- if (propertyValue is Enum)
- {
- enumTypeConvertedValue = ((int)propertyValue).ToString();
- }
- else
- {
- enumTypeConvertedValue = propertyValue.ToString();
- }
- if (propertyValueFixedLength.HasValue)
- {
- if (long.Parse(enumTypeConvertedValue) > int.MaxValue)
- {
- return long.Parse(enumTypeConvertedValue).GetBinBytes(propertyValueFixedLength.Value);
- }
- else
- {
- return (int.Parse(enumTypeConvertedValue)).GetBinBytes(propertyValueFixedLength.Value);
- }
- }
- else
- {
- logger.Info($"Parsing value: {enumTypeConvertedValue}");
- return (int.Parse(enumTypeConvertedValue)).GetBinBytes(1);
- }
- break;
- case EncodingType.HexString:
- return StringToByteArray(propertyValue.ToString());
- break;
- }
- return null;
- }
- public static byte[] StringToByteArray(string hex)
- {
- int numberChars = hex.Length;
- byte[] bytes = new byte[numberChars / 2];
- for (int i = 0; i < numberChars; i += 2)
- bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
- return bytes;
- }
- public static string ByteArrayToString(byte[] arr)
- {
- return BitConverter.ToString(arr).Replace("-", "");
- }
- static bool IsNullable<T>(T obj)
- {
- if (obj == null) return true; // obvious
- Type type = typeof(T);
- if (!type.IsValueType) return true; // ref-type
- if (Nullable.GetUnderlyingType(type) != null) return true; // Nullable<T>
- return false; // value-type
- }
- private byte[] ConvertToBytes(object elementInstance)
- {
- // Only processing MessageAttribute marked properties, and order by its Index in DESC, like 10, 9, 8, 7.... -1, -2...-10.
- var targetPropertyList = elementInstance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
- .Where(p => p.GetCustomAttributes(typeof(AttributeBase), true).Length > 0)
- .OrderByDescending(info => ((AttributeBase)(info.GetCustomAttributes(typeof(AttributeBase), true)[0])).Index);
- foreach (var propertyInfo in targetPropertyList)
- {
- // normal Format Property
- if (propertyInfo.GetCustomAttributes(typeof(EnumerableFormatAttribute), true).Length == 0
- && propertyInfo.GetCustomAttributes(typeof(FormatAttribute), true).Length > 0)
- {
- #region validate the Range Attibute marked property, if existed and failed to pass, throw exception
- // Validate the range, once failed, throw a exception
- var propertyRangeAttributes = propertyInfo.GetCustomAttributes(typeof(RangeAttribute), true);
- if (propertyRangeAttributes.Length > 0)
- {
- var rangeAttribute = (RangeAttribute)propertyRangeAttributes[0];
- if (rangeAttribute != null)
- {
- if (!rangeAttribute.IsValid(propertyInfo.GetValue(elementInstance, null)))
- {
- throw new ArgumentException(rangeAttribute.ErrorMessage);
- }
- }
- }
- #endregion
- var format =
- (FormatAttribute)propertyInfo.GetCustomAttributes(typeof(FormatAttribute), true)[0];
- if (format.LengthOrCountLinkExpression != null
- && format.LengthOrCountLinkExpression.Contains("%OnSerializingBytesCount")
- )
- {
- var lengthValue = this.processingRawDataUsedInSerialize.Length;
- // if this property's value is default with null (most likely the nullable int), then will not set any value on it, the serializing
- // result will omit this property.
- if (lengthValue == 0 && propertyInfo.GetValue(elementInstance, null) == null)
- {
- }
- else
- propertyInfo.SetValue(elementInstance, this.processingRawDataUsedInSerialize.Length, null);
- }
- else if (!string.IsNullOrEmpty(format.LengthOrCountLink))
- {
- var lengthLinkProperty = targetPropertyList.FirstOrDefault(t => t.Name == format.LengthOrCountLink);
- if (lengthLinkProperty != null)
- {
- propertyInfo.SetValue(elementInstance, propertyInfo.GetValue(elementInstance, null).ToString().Length, null);
- }
- }
- var propertyValue = propertyInfo.GetValue(elementInstance, null);
- // if this property value is null, then indicate it's a optional field, then skip to parsing it.
- if (propertyValue == null)
- {
- var safeEvent = this.FieldSerializing;
- safeEvent?.Invoke(this, new ParsingEventArg<string, byte[]>() { From = propertyInfo.Name, To = null });
- continue;
- }
- var safe = this.FieldSerializing;
- safe?.Invoke(this, new ParsingEventArg<string, byte[]>() { From = propertyInfo.Name, To = null });
- byte[] currentPropertyBytes = this.SerializePropertyData(format.EncodingType,
- propertyValue, format.FixedLengthOrCount > 0 ? format.FixedLengthOrCount : default(int?),
- elementInstance);
- safe = this.FieldSerialized;
- safe?.Invoke(this, new ParsingEventArg<string, byte[]>() { From = propertyInfo.Name, To = currentPropertyBytes });
- this.processingRawDataUsedInSerialize = this.processingRawDataUsedInSerialize.AppendToHeader(currentPropertyBytes);
- if (format.Index >= 0) this.positiveIndexAccu += currentPropertyBytes.Length;
- // try fill the LengthLink field.
- //if (!string.IsNullOrEmpty(format.LengthOrCountLink))
- //{
- // var lengthLinkProperty = targetPropertyList.FirstOrDefault(t => t.Name == format.LengthOrCountLink);
- // if (lengthLinkProperty != null
- // && string.IsNullOrEmpty(format.LengthOrCountLinkExpression)
- // // if it's not a LengthOrCountLinkExpression field, then the linking field is directly reflected the length, so it must euqal 0
- // // if not, means the users code set the length explicitly, accept this behavior to NOT overwirte it.
- // && int.Parse(lengthLinkProperty.GetValue(elementInstance, null).ToString()) == 0)
- // {
- // lengthLinkProperty.SetValue(elementInstance, currentPropertyBytes.Length, null);
- // }
- // else if (false && !string.IsNullOrEmpty(format.LengthOrCountLinkExpression))
- // {
- // var pair = format.LengthOrCountLinkExpression.Split(';');
- // bool found = false;
- // foreach (string p in pair)
- // {
- // var mappings = p.Split(':');
- // if (currentPropertyBytes.Length == int.Parse(mappings[1]))
- // {
- // //lengthLinkProperty.SetValue(elementInstance, int.Parse(mappings[0], null));
- // lengthLinkProperty.SetValue(elementInstance, int.Parse(mappings[0], null), null);
- // found = true;
- // break;
- // }
- // }
- // if (!found)
- // { throw new ArgumentException("Serializing LengthOrCountLinkExpression: " + format.LengthOrCountLinkExpression + " didn't found match values in link field: " + format.LengthOrCountLink); }
- // }
- //}
- }
- // the Enumerable Property
- else if (propertyInfo.GetCustomAttributes(typeof(EnumerableFormatAttribute), true).Length > 0)
- {
- var enumerableFormat =
- (EnumerableFormatAttribute)propertyInfo.GetCustomAttributes(typeof(EnumerableFormatAttribute), true)[0];
- // for now, we only support List<T>
- if (propertyInfo.PropertyType.IsAssignableFrom(typeof(IList)))
- {
- throw new ArgumentException("For now, we only support Enumerable of List<T>");
- }
- var list = (IList)propertyInfo.GetValue(elementInstance, null);
- if (list != null)
- {
- if (enumerableFormat.FixedLengthOrCount > 0)
- {
- if (enumerableFormat.FixedLengthOrCount != list.Count)
- {
- throw new ArgumentException("The current count(" + list.Count + ") of EnumerableFormat property: "
- + propertyInfo.Name + " must match its declared FixedLengthOrCount(" + enumerableFormat.FixedLengthOrCount + ")");
- }
- }
- if (list.GetType().GetGenericArguments()[0].IsPrimitive)
- {
- var safe = this.FieldSerializing;
- if (safe != null) { safe(this, new ParsingEventArg<string, byte[]>() { From = propertyInfo.Name, To = null }); }
- byte[] primitiveSerilaizedBytes = new byte[0];
- for (int i = list.Count - 1; i >= 0; i--)
- {
- primitiveSerilaizedBytes = primitiveSerilaizedBytes.AppendToHeader(
- this.SerializePropertyData(
- enumerableFormat.EncodingType,
- list[i],
- 1,
- elementInstance)
- );
- }
- this.processingRawDataUsedInSerialize = this.processingRawDataUsedInSerialize.AppendToHeader(primitiveSerilaizedBytes);
- if (enumerableFormat.Index >= 0) this.positiveIndexAccu += primitiveSerilaizedBytes.Length;
- safe = this.FieldSerialized;
- if (safe != null) { safe(this, new ParsingEventArg<string, byte[]>() { From = propertyInfo.Name, To = primitiveSerilaizedBytes }); }
- }
- else
- {
- // reverse
- for (int i = list.Count - 1; i >= 0; i--)
- {
- this.processingRawDataUsedInSerialize.AppendToHeader(this.ConvertToBytes(list[i]));
- }
- }
- if (!string.IsNullOrEmpty(enumerableFormat.LengthOrCountLink) && enumerableFormat.LengthOrCountLink.ToLower() != "%cascade")
- {
- var countLinkProperty = targetPropertyList.FirstOrDefault(t => t.Name == enumerableFormat.LengthOrCountLink);
- if (countLinkProperty != null)
- {
- try
- {
- countLinkProperty.SetValue(elementInstance, list.Count, null);
- }
- catch (Exception ex)
- {
- }
- }
- }
- else
- {
- if (!string.IsNullOrEmpty(enumerableFormat.LengthOrCountLink) && enumerableFormat.LengthOrCountLink.ToLower() != "%cascade")
- {
- targetPropertyList.First(t => t.Name == enumerableFormat.LengthOrCountLink).
- SetValue(elementInstance, 0, null);
- }
- }
- }
- }
- }
- return this.processingRawDataUsedInSerialize;
- }
- // make sure the Indexes property of one element including the sub elements
- // of its list structures are sequential and unique
- protected void CheckAttributeIndexes(object elementInstance)
- {
- if (elementInstance == null)
- {
- return;
- }
- // Only processing WayneAttribute marked properties, and order by its Index in DESC
- var targetPropertyList = elementInstance.GetType()
- .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
- .Where(p => p.GetCustomAttributes(typeof(AttributeBase), true).Length > 0)
- .OrderByDescending(
- info =>
- ((AttributeBase)(info.GetCustomAttributes(typeof(AttributeBase), true)[0]))
- .Index);
- // make sure no duplicated Index defined.
- targetPropertyList.GroupBy(
- p => ((AttributeBase)(p.GetCustomAttributes(typeof(AttributeBase), true)[0])).Index)
- .ToList()
- .ForEach(g =>
- {
- if (g.Count() > 1)
- {
- throw new ArgumentException(
- string.Format("Duplicated Index: {0} defined, make sure the Index value unique", g.Key));
- }
- });
- // make sure the Indexes are contiguously sequential and count from 0
- if (targetPropertyList.Max(
- p => ((AttributeBase)(p.GetCustomAttributes(typeof(AttributeBase), true)[0])).Index) !=
- (targetPropertyList.Count() - 1))
- {
- throw new ArgumentException(string.Format("Index must be in sequential and count from 0"));
- }
- // recursively check the sub element type of ListPropertyFormatAttribute
- foreach (var propertyInfo in targetPropertyList)
- {
- // the IList Property
- if (propertyInfo.GetCustomAttributes(typeof(EnumerableFormatAttribute), true).Length != 0)
- {
- var subElementInstance = Activator.CreateInstance(propertyInfo.PropertyType.GetGenericArguments()[0]);
- CheckAttributeIndexes(subElementInstance);
- }
- }
- }
- }
- }
|