FixAntenna/NetCore/Message/FixMessageAdapter.cs (336 lines of code) (raw):

// Copyright (c) 2021 EPAM Systems // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System; using System.Collections.Generic; using Epam.FixAntenna.NetCore.Message.Format; using Epam.FixAntenna.NetCore.Message.Storage; namespace Epam.FixAntenna.NetCore.Message { public abstract class FixMessageAdapter : AbstractFixMessage, IList<TagValue> { private readonly TagValueStorage _fields = new TagValueStorage(InitMaxFields); #region ctor protected internal FixMessageAdapter() { } protected internal FixMessageAdapter(bool isUserOwned) : base(isUserOwned) { } #endregion #region IList implementation /// <summary> /// Gets or sets <see cref="TagValue"/> field. /// </summary> /// <param name="index">Index of the field.</param> /// <returns></returns> public TagValue this[int index] { get => GetByIndex(index); set => SetByIndex(index, value); } public int IndexOf(TagValue item) { return GetTagIndex(item.TagId); } public void Insert(int index, TagValue item) { AddAtIndex(index, item); } public void RemoveAt(int index) { RemoveTagAtIndex(index); } #endregion #region ICollection implementation public bool IsReadOnly => false; void ICollection<TagValue>.Add(TagValue item) { AddTag(item); } public override void Clear() { ReleaseFixFieldCache(); base.Clear(); } public bool Contains(TagValue item) { return item != null && IsTagExists(item.TagId); } public void CopyTo(TagValue[] array, int arrayIndex) { if (array == null) { throw new ArgumentNullException(nameof(array)); } if (arrayIndex < 0) { throw new ArgumentOutOfRangeException(nameof(arrayIndex)); } var size = Count; if (size > array.Length - arrayIndex) { throw new ArgumentException(); } for (var i = 0; i < size; i++) { array[i + arrayIndex] = this[i]; } } public bool Remove(TagValue item) { return item != null && RemoveTag(item.TagId); } #endregion public override IEnumerator<TagValue> GetEnumerator() { return base.GetTagValueIterator(); } protected internal override void OnEnlarge(int ratio, int newSize) { _fields.Enlarge(ratio); } #region UpdateValue staff ... public override void UpdateValueAtIndex(int index, byte[] value) { base.UpdateValueAtIndex(index, value); _fields.InvalidateCachedField(index); } public override void UpdateCalendarValueAtIndex(int index, DateTimeOffset value, FixDateFormatterFactory.FixDateType type) { base.UpdateCalendarValueAtIndex(index, value, type); _fields.InvalidateCachedField(index); } public override void UpdateValueAtIndex(int index, double value, int precision) { base.UpdateValueAtIndex(index, value, precision); _fields.InvalidateCachedField(index); } public override void UpdateValueAtIndex(int index, long value) { base.UpdateValueAtIndex(index, value); _fields.InvalidateCachedField(index); } public override void UpdateValueAtIndex(int index, string str) { base.UpdateValueAtIndex(index, str); _fields.InvalidateCachedField(index); } public override void UpdateValueAtIndex(int index, bool value) { base.UpdateValueAtIndex(index, value); _fields.InvalidateCachedField(index); } public override void UpdateValueAtIndex(int index, byte[] value, int offset, int length) { base.UpdateValueAtIndex(index, value, offset, length); _fields.InvalidateCachedField(index); } public override void UpdateValueAtIndex(int index, TagValue value) { base.UpdateValueAtIndex(index, value); _fields.InvalidateCachedField(index); } #endregion public override bool RemoveTagAtIndex(int tagIndex, bool shiftRg) { InvalidateCachedFieldByIndex(tagIndex); return base.RemoveTagAtIndex(tagIndex, shiftRg); } public override int ReserveTagAtIndex(int addAtIndex, int tagId) { var fieldCount = base.ReserveTagAtIndex(addAtIndex, tagId); _fields.Shift(addAtIndex, 1, fieldCount); return fieldCount; } protected internal override void DeepCopy(IndexedStorage source) { base.DeepCopy(source); //TODO: in general FIXFields count could be less then overall fields count _fields.EnlargeTo(source.GetIndexCapacity()); } /// <summary> /// Use addAllTags() instead of this /// </summary> /// <param name="c"> /// @return </param> public bool AddAll(ICollection<TagValue> c) { if (c is FixMessage list) { return base.AddAll(list); } foreach (var tagValue in c) { Add(tagValue); } return true; } public virtual bool AddAll(int index, ICollection<TagValue> c) { //TODO: optimize foreach (var tagValue in c) { Add(index++, tagValue); } return true; } public virtual void Add(FixMessage list) { base.AddAll(list); } public virtual void Add(int index, TagValue element) { AddTagAtIndex(index, element); } public void AddAtIndex(int addAtIndex, TagValue field) { AddTagAtIndex(addAtIndex, field.TagId, field.Buffer, field.Offset, field.Length); } public int Length => Count; //TODO: move to field storage private void ReleaseFixFieldCache() { for (var i = 0; i < Count; i++) { _fields.InvalidateCachedField(i); } } /// <summary> /// Gets <see cref="TagValue"/> field by provided TagId value. /// </summary> /// <remarks>Seems to be main method to get field.</remarks> /// <param name="tagId"></param> /// <returns>Returns <see cref="TagValue"/> with specified TagId or null, if TagId was not found.</returns> public TagValue GetTag(int tagId) { return GetByTagId(tagId); } public TagValue GetTag(int tagId, int occurrence) { return GetByTagId(tagId, occurrence); } /// <summary> /// Gets <see cref="TagValue"/> field by provided TagId value. /// </summary> /// <remarks>Seems to be main method to get field.</remarks> /// <param name="tagId"></param> /// <returns>Returns <see cref="TagValue"/> with specified TagId or null, if TagId was not found.</returns> private TagValue GetByTagId(int tagId) { var index = GetTagIndex(tagId); return index == IndexedStorage.NotFound ? null : GetByIndex(index); } private TagValue GetByTagId(int tagId, int occurrence) { var index = GetTagIndex(tagId, occurrence); return index == IndexedStorage.NotFound ? null : GetByIndex(index); } private TagValue GetByIndex(int index) { TagValue field = null; if (index >= 0 && Count >= index) { field = _fields[index]; if (field == null) { field = GetByIndex(index, IsOriginatingFromPool); _fields[index] = field; } } return field; } public bool TryGetLongByIndex(int index, out long value) { value = 0; var tv = GetByIndex(index); return tv != null && FixTypes.TryParseLong(tv.Buffer, tv.Offset, tv.Length, out value); } private TagValue GetByIndex(int index, bool isFromPool) { var storage = GetStorage(index); var fieldData = storage.GetByteArray(index); var fieldDataLength = GetTagValueLengthAtIndex(index); var fieldDataOffset = GetTagValueOffsetAtIndex(index); var tagId = GetTagIdAtIndex(index); if (isFromPool) { var data = RawFixUtil.CopyValueUsePool(fieldData, fieldDataOffset, fieldDataLength); var field = RawFixUtil.GetFieldFromPool(); field.Reload(tagId, data, true); return field; } return new TagValue(tagId, RawFixUtil.CopyValue(fieldData, fieldDataOffset, fieldDataLength), true); } private void SetByIndex(int index, TagValue newValue) { Remove(index); Add(index, newValue); } public virtual TagValue Remove(int index) { var tagValue = this[index]; RemoveTagAtIndex(index); return tagValue; } /// <summary> /// Utility method that splits current message into the repeating /// groups based on first mandatory tag in the repeating /// group (always first tag in the repeating group). /// </summary> /// <param name="tag"> the tag number </param> /// <returns> List of repeating groups (each one is separate FixMessage) </returns> public IList<List<TagValue>> SplitAsList(int tag) { var append = false; var result = new List<List<TagValue>>(); List<TagValue> list = null; //TBD! optimize for (var i = 0; i < Count; i++) { if (base.GetTagIdAtIndex(i) == tag) { if (list != null) { result.Add(list); } list = new List<TagValue>(); append = true; } if (append) { list.Add(GetTag(i)); } } if (list != null) { result.Add(list); } return result; } /// <summary> /// Parse repeating group in FIX message /// </summary> /// <param name="rgTag"> Group amount tag </param> /// <param name="rgFirstTag"> The first tag. Tag just after size tag. </param> /// <param name="tagList"> List of expected tags </param> /// <returns> Repeating group </returns> public IList<IDictionary<int, TagValue>> ExtractGroup(int rgTag, int rgFirstTag, int[] tagList) { var rgTagIndex = GetTagIndex(rgTag); if (rgTagIndex < 0) { return new List<IDictionary<int, TagValue>>(); } var noGrp = (int)GetByIndex(rgTagIndex).LongValue; var lst = new List<IDictionary<int, TagValue>>(noGrp); for (var i = 0; i < noGrp; i++) { lst.Add(new Dictionary<int, TagValue>(tagList.Length)); } Array.Sort(tagList); var rgCounter = -1; for (var i = rgTagIndex + 1; i < Count; i++) { var field = this[i]; var tag = field.TagId; if (tag == rgFirstTag) { rgCounter++; if (rgCounter >= noGrp) { break; // we was read all RG entries of this RG } } if (Array.BinarySearch(tagList, tag) != -1) { if (rgCounter == -1) { return NotifyInvalidMessage(rgTag, rgFirstTag); } lst[rgCounter][tag] = field; } } return lst; } public int GetTagAsInt(int tag) { return (int)GetTagValueAsLong(tag); } public int GetTagAsInt(int tag, int occurrence) { return (int)GetTagValueAsLong(tag, occurrence); } public int GetTagAsIntAtIndex(int index) { return (int)GetTagValueAsLongAtIndex(index); } private void InvalidateCachedFieldByIndex(int tagIndex) { _fields.InvalidateCachedField(tagIndex); var size = base.Count; if (tagIndex < size - 1) { _fields.ShiftBack(tagIndex, 1, size); } } } }