FixAntenna/NetCore/Message/Rg/EntryImpl.cs (1,027 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 System.Text; using Epam.FixAntenna.NetCore.Helpers; using Epam.FixAntenna.NetCore.Message.Format; using Epam.FixAntenna.NetCore.Message.Rg.Exceptions; using static Epam.FixAntenna.NetCore.Message.Rg.RepeatingGroupStorage; namespace Epam.FixAntenna.NetCore.Message.Rg { public class EntryImpl { private HiddenLeadingTagsArray _hiddenLeadingTagsArray; protected internal List<RepeatingGroup> AllocatedSubGroups = new List<RepeatingGroup>(); protected internal bool Deleted; /*protected*/ internal EntriesArray EntriesArray; protected internal int EntryIndex; protected internal RepeatingGroup Group; protected internal HashSet<int> GroupTags; protected internal HashSet<int> NestedLeadingTags; protected internal HashSet<int> OuterLeadingTags; protected internal bool ReleaseNeeded = true; /*protected*/ internal RepeatingGroupArray RgArray; /*protected*/ internal RepeatingGroupStorage RgStorage; protected internal IndexedStorage Storage; /// <summary> /// Adds sub group to entry. Validation for this group is turned off. </summary> /// <param name="leadingTag"> leading tag for repeating group. </param> /// <returns> instance of RepeatingGroup from RepeatingGroupPool. There is no need to call release for this object. </returns> public virtual RepeatingGroup AddRepeatingGroup(int leadingTag) { return AddRepeatingGroup(leadingTag, Group.Validation); } public virtual void AddRepeatingGroup(int leadingTag, RepeatingGroup group) { AddRepeatingGroup(leadingTag, false, group); } /// <summary> /// Adds sub group to entry. </summary> /// <param name="leadingTag"> leading tag for repeating group. </param> /// <param name="validation"> turn on/off validation </param> /// <returns> instance of RepeatingGroup from RepeatingGroupPool. There is no need to call release for this object. </returns> public virtual RepeatingGroup AddRepeatingGroup(int leadingTag, bool validation) { var subGroup = RepeatingGroupPool.RepeatingGroup; subGroup.ReleaseNeeded = false; AllocatedSubGroups.Add(subGroup); AddRepeatingGroup(leadingTag, validation, subGroup); return subGroup; } /// <summary> /// Adds sub group to entry. </summary> /// <param name="leadingTag"> leading tag for repeating group. </param> /// <param name="validation"> turn on/off validation. </param> /// <param name="subGroup"> group for further work. </param> public virtual void AddRepeatingGroup(int leadingTag, bool validation, RepeatingGroup subGroup) { if (Deleted) { throw new InvalidOperationException("Entry was deleted. You should create new entry"); } if (Group.Validation) { IsTagValid(leadingTag); IsLeadingTagValid(leadingTag); } IsDuplicateGroup(leadingTag); var entry = RgStorage.EnsureEntryCapacityAndEnlarge(EntryIndex); var repeatingGroup = RgArray.GetRepeatingGroup(Group.RgIndex); int lastIndex; if (EntriesArray.IsEmpty(entry)) { if (RgArray.GetRgLeadingTagIndexInFixMsg(repeatingGroup) == -1) { var indexInHiddenLeadingTags = _hiddenLeadingTagsArray.FindInHidedLeadingTags(Group.LeadingTag, Group.RgId); lastIndex = _hiddenLeadingTagsArray.GetTagLinkVirtual(indexInHiddenLeadingTags) + 1; } else if (EntriesArray.HasParent(entry)) { var parentEntry = EntriesArray.GetParentEntry(entry); lastIndex = EntriesArray.GetLastTagIndexInFixMessage(parentEntry); } else { var prevEntry = RgArray.GetPrevEntryLink(repeatingGroup, EntryIndex); lastIndex = EntriesArray.GetLastTagIndexInFixMessage(EntriesArray.GetEntry(prevEntry)) + 1; while (EntriesArray.IsEmpty(prevEntry)) { prevEntry = RgArray.GetPrevEntryLink(repeatingGroup, prevEntry); lastIndex = EntriesArray.GetLastTagIndexInFixMessage(EntriesArray.GetEntry(prevEntry)) + 1; } } } else { lastIndex = 1 + EntriesArray.GetLastTagIndexInFixMessage(entry); } RgStorage.AddSubGroup(lastIndex, leadingTag, EntryIndex, subGroup, validation); } /// <summary> /// Adds tagValue to entry. </summary> /// <param name="tagValue"> value for add. </param> /// <returns> index in message (not in group or entry) in which value was added </returns> public virtual int AddTag(TagValue tagValue) { var index = PrepareAdd(tagValue.TagId); Storage.UpdateValueAtIndex(index, tagValue); return index; } /// <summary> /// Adds tag to entry. </summary> /// <param name="tag"> tag for add </param> /// <param name="value"> value for add </param> /// <returns> index in message (not in group or entry) in which value was added </returns> public virtual int AddTag(int tag, byte[] value) { var index = PrepareAdd(tag); Storage.UpdateValueAtIndex(index, value); return index; } /// <summary> /// Adds tag to entry. </summary> /// <param name="tag"> tag for add </param> /// <param name="value"> value for add </param> /// <returns> index in message (not in group or entry) in which value was added </returns> public virtual int AddTag(int tag, char value) { var index = PrepareAdd(tag); //TBD! implement in right way! var bytes = new[] { (byte)value }; Storage.UpdateValueAtIndex(index, bytes); return index; } /// <summary> /// Adds tag to entry. </summary> /// <param name="tag"> tag for add </param> /// <param name="value"> value for add </param> /// <returns> index in message (not in group or entry) in which value was added </returns> public virtual int AddTag(int tag, bool value) { var index = PrepareAdd(tag); Storage.UpdateValueAtIndex(index, value); return index; } /// <summary> /// Adds tag to entry. </summary> /// <param name="tag"> </param> /// <param name="value"> byte array for add </param> /// <param name="offset"> offset in passed array </param> /// <param name="length"> length of array that will be added </param> /// <returns> index in message (not in group or entry) in which value was added </returns> public virtual int AddTag(int tag, byte[] value, int offset, int length) { var index = PrepareAdd(tag); Storage.UpdateValueAtIndex(index, value, offset, length); return index; } /// <summary> /// Adds tag to entry. </summary> /// <param name="tag"> tag for add </param> /// <param name="value"> value for add </param> /// <param name="precision"> precision of value rounding </param> /// <returns> index in message (not in group or entry) in which value was added </returns> public virtual int AddTag(int tag, double value, int precision) { var index = PrepareAdd(tag); Storage.UpdateValueAtIndex(index, value, precision); return index; } /// <summary> /// Adds tag to entry. </summary> /// <param name="tag"> tag for add </param> /// <param name="value"> value for add </param> /// <returns> index in message (not in group or entry) in which value was added </returns> public virtual int AddTag(int tag, long value) { var index = PrepareAdd(tag); Storage.UpdateValueAtIndex(index, value); return index; } /// <summary> /// Adds tag to entry. </summary> /// <param name="tag"> tag for add </param> /// <param name="value"> value for add </param> /// <returns> index in message (not in group or entry) in which value was added </returns> public virtual int AddTag(int tag, string value) { var index = PrepareAdd(tag); Storage.UpdateValueAtIndex(index, value); return index; } /// <summary> /// Adds tag to entry. </summary> /// <param name="tag"> tag for add </param> /// <param name="value"> value for add </param> /// <param name="type"> type of date </param> /// <returns> index in message (not in group or entry) in which value was added </returns> public virtual int AddCalendarTag(int tag, DateTimeOffset value, FixDateFormatterFactory.FixDateType type) { var index = PrepareAdd(tag); Storage.UpdateCalendarValueAtIndex(index, value, type); return index; } private int PrepareAdd(int tag) { if (Deleted) { throw new InvalidOperationException("Entry was deleted. You should create new entry"); } if (Group.Validation) { IsTagValid(tag); IsDuplicateTag(tag); } var entry = RgStorage.EnsureEntryCapacityAndEnlarge(EntryIndex); var entries = RgStorage.Entries; CreateRepeatingGroupIfNotExists(entry, Group.RgIndex, EntryIndex); EntriesArray.IncrementLastTagIndexInFixMessage(entry); var lastIndex = EntriesArray.GetLastTagIndexInFixMessage(entry); RgStorage.PrepareAdd(lastIndex, tag, Group.ParentEntryIndex, EntryIndex); EntriesArray.AddEntry(entry, tag, lastIndex, LinkTypeTag); Group.UpdateParentEntries(entry, entries, lastIndex, 1); return lastIndex; } private int[] CreateRepeatingGroupIfNotExists(int[] entry, int rgIndex, int entryIndex) { if (EntriesArray.HasParent(entry) && EntriesArray.IsEmpty(EntriesArray.GetParentEntry(entry))) { //Should create all uncreated parent repeating group var parentEntryLink = EntriesArray.GetParentEntryLink(entry); var parentEntry = EntriesArray.GetParentEntry(entry); var parentRgArrayIndex = RgArray.GetRgIndexByEntryIndex(parentEntryLink); CreateRepeatingGroupIfNotExists(parentEntry, parentRgArrayIndex, parentEntryLink); } int[] repeatingGroup; if (rgIndex == -1) { throw new InvalidOperationException( "Entry, which owned repeating group, was deleted. You should create new entry with new nested group."); } repeatingGroup = RgStorage.RepeatingGroups[rgIndex]; var leadingTag = RgArray.GetRgLeadingTag(repeatingGroup); var rgId = RgArray.GetRgId(repeatingGroup); var parentEntryIndex = RgArray.GetParentEntryIndex(repeatingGroup); //If group is not created, then recreate this group if (rgId == -1) { var hiddenArrayIndex = _hiddenLeadingTagsArray.FindInHidedLeadingTags(leadingTag, rgId); rgId = _hiddenLeadingTagsArray.GetRgId(hiddenArrayIndex); RgStorage.ReAddRgToArray(rgIndex, rgId); // rgArray.addEntry(repeatingGroup, entryIndex); } //if first tag in entry then should update leading tag and set last tag link in new entry if (EntriesArray.IsEmpty(entryIndex)) { RgStorage.IncrementLeadingTag(RgStorage.GetLeadingTagValue(leadingTag, rgId) + 1, leadingTag, rgId, parentEntryIndex, parentEntryIndex, rgIndex); var lastTagLink = RgArray.GetRgLeadingTagIndexById(leadingTag, rgId); if (RgArray.GetRgLeadingTagIndexInFixMsg(repeatingGroup) != -1) { var prevEntry = RgArray.GetPrevEntryLink(repeatingGroup, entryIndex); while (prevEntry != -1 && EntriesArray.IsEmpty(prevEntry)) { prevEntry = RgArray.GetPrevEntryLink(repeatingGroup, prevEntry); } if (prevEntry != -1 && !EntriesArray.IsEmpty(prevEntry)) { lastTagLink = EntriesArray.GetLastTagIndexInFixMessage(EntriesArray.GetEntry(prevEntry)); } } EntriesArray.SetLastTagIndexInFixMessage(entry, lastTagLink); } return repeatingGroup; } /// <summary> /// Updates tagValue </summary> /// <param name="tagValue"> tagValue for update </param> /// <param name="addIfNotExists"> determines the behavior in case if tag is not exists </param> /// <returns> index of updated value in fix message (not in group or entry) </returns> public virtual int UpdateValue(TagValue tagValue, IndexedStorage.MissingTagHandling addIfNotExists) { var tag = tagValue.TagId; ValidateUpdateLeadingTag(tag); switch (addIfNotExists) { case IndexedStorage.MissingTagHandling.DontAddIfNotExists: var tagLink = FindTagLink(tag); if (tagLink != -1) { Storage.UpdateValueAtIndex(tagLink, tagValue); } return tagLink; case IndexedStorage.MissingTagHandling.AddIfNotExists: var link = FindTagLink(tag); if (link == -1) { return AddTag(tagValue); } else { Storage.UpdateValueAtIndex(link, tagValue); return link; } case IndexedStorage.MissingTagHandling.AlwaysAdd: return AddTag(tagValue); default: throw new NotSupportedException("Unsupported tag handling " + addIfNotExists); } } /// <summary> /// Updates tag value </summary> /// <param name="tag"> tag for update </param> /// <param name="value"> new value for tag </param> /// <param name="offset"> offset in passed array </param> /// <param name="length"> length of array that will be added </param> /// <param name="addIfNotExists"> determines the behavior in case if tag is not exists </param> /// <returns> index of updated value in fix message (not in group or entry) </returns> public virtual int UpdateValue(int tag, byte[] value, int offset, int length, IndexedStorage.MissingTagHandling addIfNotExists) { ValidateUpdateLeadingTag(tag); switch (addIfNotExists) { case IndexedStorage.MissingTagHandling.DontAddIfNotExists: var linkTag = FindTagLink(tag); if (linkTag != -1) { Storage.UpdateValueAtIndex(linkTag, value, offset, length); } return linkTag; case IndexedStorage.MissingTagHandling.AddIfNotExists: var link = FindTagLink(tag); if (link == -1) { return AddTag(tag, value, offset, length); } else { Storage.UpdateValueAtIndex(link, value, offset, length); return link; } case IndexedStorage.MissingTagHandling.AlwaysAdd: return AddTag(tag, value, offset, length); default: throw new NotSupportedException("Unsupported tag handling " + addIfNotExists); } } /// <summary> /// Updates tag value </summary> /// <param name="tag"> tag for update </param> /// <param name="value"> new value for tag </param> /// <param name="addIfNotExists"> determines the behavior in case if tag is not exists </param> /// <returns> index of updated value in fix message (not in group or entry) </returns> public virtual int UpdateValue(int tag, byte[] value, IndexedStorage.MissingTagHandling addIfNotExists) { ValidateUpdateLeadingTag(tag); switch (addIfNotExists) { case IndexedStorage.MissingTagHandling.DontAddIfNotExists: var linkTag = FindTagLink(tag); if (linkTag != -1) { Storage.UpdateValueAtIndex(linkTag, value); } return linkTag; case IndexedStorage.MissingTagHandling.AddIfNotExists: var link = FindTagLink(tag); if (link == -1) { return AddTag(tag, value); } else { Storage.UpdateValueAtIndex(link, value); return link; } case IndexedStorage.MissingTagHandling.AlwaysAdd: return AddTag(tag, value); default: throw new NotSupportedException("Unsupported tag handling " + addIfNotExists); } } /// <summary> /// Updates tag value </summary> /// <param name="tag"> tag for update </param> /// <param name="value"> new value for tag </param> /// <param name="addIfNotExists"> determines the behavior in case if tag is not exists </param> /// <returns> index of updated value in fix message (not in group or entry) </returns> public virtual int UpdateValue(int tag, long value, IndexedStorage.MissingTagHandling addIfNotExists) { ValidateUpdateLeadingTag(tag); switch (addIfNotExists) { case IndexedStorage.MissingTagHandling.DontAddIfNotExists: var linkTag = FindTagLink(tag); if (linkTag != -1) { Storage.UpdateValueAtIndex(linkTag, value); } return linkTag; case IndexedStorage.MissingTagHandling.AddIfNotExists: var link = FindTagLink(tag); if (link == -1) { return AddTag(tag, value); } else { Storage.UpdateValueAtIndex(link, value); return link; } case IndexedStorage.MissingTagHandling.AlwaysAdd: return AddTag(tag, value); default: throw new NotSupportedException("Unsupported tag handling " + addIfNotExists); } } /// <summary> /// Updates tag value </summary> /// <param name="tag"> tag for update </param> /// <param name="value"> new value for tag </param> /// <param name="precision"> precision of value rounding </param> /// <param name="addIfNotExists"> determines the behavior in case if tag is not exists </param> /// <returns> index of updated value in fix message (not in group or entry) </returns> public virtual int UpdateValue(int tag, double value, int precision, IndexedStorage.MissingTagHandling addIfNotExists) { ValidateUpdateLeadingTag(tag); switch (addIfNotExists) { case IndexedStorage.MissingTagHandling.DontAddIfNotExists: var linkTag = FindTagLink(tag); if (linkTag != -1) { Storage.UpdateValueAtIndex(linkTag, value, precision); } return linkTag; case IndexedStorage.MissingTagHandling.AddIfNotExists: var link = FindTagLink(tag); if (link == -1) { return AddTag(tag, value, precision); } else { Storage.UpdateValueAtIndex(link, value, precision); return link; } case IndexedStorage.MissingTagHandling.AlwaysAdd: return AddTag(tag, value, precision); default: throw new NotSupportedException("Unsupported tag handling " + addIfNotExists); } } /// <summary> /// Updates tag value </summary> /// <param name="tag"> tag for update </param> /// <param name="strBuffer"> new value for tag </param> /// <param name="addIfNotExists"> determines the behavior in case if tag is not exists </param> /// <returns> index of updated value in fix message (not in group or entry) </returns> public virtual int UpdateValue(int tag, string strBuffer, IndexedStorage.MissingTagHandling addIfNotExists) { ValidateUpdateLeadingTag(tag); switch (addIfNotExists) { case IndexedStorage.MissingTagHandling.DontAddIfNotExists: var linkTag = FindTagLink(tag); if (linkTag != -1) { Storage.UpdateValueAtIndex(linkTag, strBuffer); } return linkTag; case IndexedStorage.MissingTagHandling.AddIfNotExists: var link = FindTagLink(tag); if (link == -1) { return AddTag(tag, strBuffer); } else { Storage.UpdateValueAtIndex(link, strBuffer); return link; } case IndexedStorage.MissingTagHandling.AlwaysAdd: return AddTag(tag, strBuffer); default: throw new NotSupportedException("Unsupported tag handling " + addIfNotExists); } } /// <summary> /// Updates tag value </summary> /// <param name="tag"> tag for update </param> /// <param name="value"> new value for tag </param> /// <param name="addIfNotExists"> determines the behavior in case if tag is not exists </param> /// <returns> index of updated value in fix message (not in group or entry) </returns> public virtual int UpdateValue(int tag, bool value, IndexedStorage.MissingTagHandling addIfNotExists) { ValidateUpdateLeadingTag(tag); switch (addIfNotExists) { case IndexedStorage.MissingTagHandling.DontAddIfNotExists: var linkTag = FindTagLink(tag); if (linkTag != -1) { Storage.UpdateValueAtIndex(linkTag, value); } return linkTag; case IndexedStorage.MissingTagHandling.AddIfNotExists: var link = FindTagLink(tag); if (link == -1) { return AddTag(tag, value); } else { Storage.UpdateValueAtIndex(link, value); return link; } case IndexedStorage.MissingTagHandling.AlwaysAdd: return AddTag(tag, value); default: throw new NotSupportedException("Unsupported tag handling " + addIfNotExists); } } /// <summary> /// Updates tag value </summary> /// <param name="tag"> tag for update </param> /// <param name="value"> new value for tag </param> /// <param name="type"> type of date </param> /// <param name="addIfNotExists"> determines the behavior in case if tag is not exists </param> /// <returns> index of updated value in fix message (not in group or entry) </returns> public virtual int UpdateCalendarValue(int tag, DateTimeOffset value, FixDateFormatterFactory.FixDateType type, IndexedStorage.MissingTagHandling addIfNotExists) { ValidateUpdateLeadingTag(tag); switch (addIfNotExists) { case IndexedStorage.MissingTagHandling.DontAddIfNotExists: var linkTag = FindTagLink(tag); if (linkTag != -1) { Storage.UpdateCalendarValueAtIndex(linkTag, value, type); } return linkTag; case IndexedStorage.MissingTagHandling.AddIfNotExists: var link = FindTagLink(tag); if (link == -1) { return AddCalendarTag(tag, value, type); } else { Storage.UpdateCalendarValueAtIndex(link, value, type); return link; } case IndexedStorage.MissingTagHandling.AlwaysAdd: return AddCalendarTag(tag, value, type); default: throw new NotSupportedException("Unsupported tag handling " + addIfNotExists); } } private void ValidateUpdateLeadingTag(int tag) { if (IsGroupTag(tag)) { throw new ArgumentException( "Trying to update leading tag value. It's impossible because leading tags are self-maintaining."); } } /// <summary> /// Returns true if tag exists in entry </summary> /// <param name="tag"> tag for check </param> /// <returns> is tag exists </returns> public virtual bool IsTagExists(int tag) { return FindTagLink(tag) != -1; } /// <summary> /// Fills passed TagValue object by tag data </summary> /// <param name="tag"> tag for find </param> /// <param name="dest"> object for fill </param> public virtual void LoadTagValue(int tag, TagValue dest) { var tagLink = FindTagLink(tag); if (tagLink != -1) { Storage.LoadTagValueByIndex(tagLink, dest); } else { throw new FieldNotFoundException("Entry doesn't contains tag " + tag); } } /// <summary> /// Returns tag value as string </summary> /// <param name="tag"> tag for find </param> /// <returns> tag value </returns> public virtual string GetTagValueAsString(int tag) { var tagLink = FindTagLink(tag); if (tagLink != -1) { return Storage.GetTagValueAsStringAtIndex(tagLink); } return null; } /// <summary> /// Returns tag value as byte array </summary> /// <param name="tag"> tag for find </param> /// <returns> tag value </returns> public virtual byte[] GetTagValueAsBytes(int tag) { var tagLink = FindTagLink(tag); if (tagLink != -1) { return Storage.GetTagValueAsBytesAtIndex(tagLink); } return null; } /// <summary> /// Returns tag value as byte array </summary> /// <param name="tag"> tag for find </param> /// <param name="dest"> array for fill </param> /// <param name="offset"> start index in passed array from which array will filled </param> /// <returns> length of wrote data </returns> public virtual int GetTagValueAsBytes(int tag, byte[] dest, int offset) { var tagLink = FindTagLink(tag); if (tagLink != -1) { return Storage.GetTagValueAsBytesAtIndex(tagLink, dest, offset); } throw new FieldNotFoundException("Entry doesn't contains tag " + tag); } /// <summary> /// Returns first byte of tag value </summary> /// <param name="tag"> tag for find </param> /// <returns> first byte of tag value </returns> public virtual byte GetTagValueAsByte(int tag) { var tagLink = FindTagLink(tag); if (tagLink != -1) { return Storage.GetTagValueAsByteAtIndex(tagLink, 0); } throw new FieldNotFoundException("Entry doesn't contains tag " + tag); } /// <summary> /// Returns byte of tag value at offset </summary> /// <param name="tag"> tag for find </param> /// <returns> byte of tag value </returns> public virtual byte GetTagValueAsByte(int tag, int offset) { var tagLink = FindTagLink(tag); if (tagLink != -1) { return Storage.GetTagValueAsByteAtIndex(tagLink, offset); } throw new FieldNotFoundException("Entry doesn't contains tag " + tag); } /// <summary> /// Returns tag value as boolean </summary> /// <param name="tag"> tag for find </param> /// <returns> tag value </returns> public virtual bool GetTagValueAsBool(int tag) { var tagLink = FindTagLink(tag); if (tagLink != -1) { return Storage.GetTagValueAsBoolAtIndex(tagLink); } throw new FieldNotFoundException("Entry doesn't contains tag " + tag); } /// <summary> /// Returns tag value as double </summary> /// <param name="tag"> tag for find </param> /// <returns> tag value </returns> public virtual double GetTagValueAsDouble(int tag) { var tagLink = FindTagLink(tag); if (tagLink != -1) { return Storage.GetTagValueAsDoubleAtIndex(tagLink); } throw new FieldNotFoundException("Entry doesn't contains tag " + tag); } /// <summary> /// Returns tag value as long </summary> /// <param name="tag"> tag for find </param> /// <returns> tag value </returns> public virtual long GetTagValueAsLong(int tag) { var tagLink = FindTagLink(tag); if (tagLink != -1) { return Storage.GetTagValueAsLongAtIndex(tagLink); } throw new FieldNotFoundException("Entry doesn't contains tag " + tag); } /// <summary> /// Fills passed StringBuffer by tag value </summary> /// <param name="tag"> tag for find </param> /// <param name="str"> buffer for filled </param> public virtual void GetTagValueAsStringBuff(int tag, StringBuilder str) { var tagLink = FindTagLink(tag); if (tagLink != -1) { Storage.GetTagValueAsStringBuffAtIndex(tagLink, str); } else { throw new FieldNotFoundException("Entry doesn't have field " + tag); } } /// <summary> /// Fills passed TagValue object by tag data from index </summary> /// <param name="index"> number of tag in entry (not in entire FIX message) </param> /// <param name="dest"> object for fill </param> public virtual void LoadTagValueByIndex(int index, TagValue dest) { var size = Count; if (index < size && index >= 0) { var entry = EntriesArray.GetEntry(EntryIndex); Storage.LoadTagValueByIndex(EntriesArray.GetTagLinkAtIndex(entry, index), dest); return; } throw new IndexOutOfRangeException("Invalid index for entry with size " + size); } /// <summary> /// Returns tag value as string </summary> /// <param name="index"> number of tag in entry (not in entire FIX message) </param> /// <returns> tag value </returns> public virtual string GetTagValueAsStringAtIndex(int index) { var size = Count; if (index < size && index >= 0) { var entry = EntriesArray.GetEntry(EntryIndex); return Storage.GetTagValueAsStringAtIndex(EntriesArray.GetTagLinkAtIndex(entry, index)); } throw new IndexOutOfRangeException("Invalid index for entry with size " + size); } /// <summary> /// Returns tag value as byte array </summary> /// <param name="index"> number of tag in entry (not in entire FIX message) </param> /// <returns> tag value </returns> public virtual byte[] GetTagValueAsBytesAtIndex(int index) { var size = Count; if (index < size && index >= 0) { var entry = EntriesArray.GetEntry(EntryIndex); return Storage.GetTagValueAsBytesAtIndex(EntriesArray.GetTagLinkAtIndex(entry, index)); } throw new IndexOutOfRangeException("Invalid index for entry with size " + size); } /// <summary> /// Returns tag value as byte array </summary> /// <param name="index"> number of tag in entry (not in entire FIX message) </param> /// <param name="dest"> array for fill </param> /// <param name="offset"> start index in passed array from which array will filled </param> /// <returns> length of wrote data </returns> public virtual int GetTagValueAsBytesAtIndex(int index, byte[] dest, int offset) { var size = Count; if (index < size && index >= 0) { var entry = EntriesArray.GetEntry(EntryIndex); return Storage.GetTagValueAsBytesAtIndex(EntriesArray.GetTagLinkAtIndex(entry, index), dest, offset); } throw new IndexOutOfRangeException("Invalid index for entry with size " + size); } /// <summary> /// Returns first byte of tag value </summary> /// <param name="index"> number of tag in entry (not in entire FIX message) </param> /// <returns> first byte of tag value </returns> public virtual byte GetTagValueAsByteAtIndex(int index) { var size = Count; if (index < size && index >= 0) { var entry = EntriesArray.GetEntry(EntryIndex); return Storage.GetTagValueAsByteAtIndex(EntriesArray.GetTagLinkAtIndex(entry, index), 0); } throw new IndexOutOfRangeException("Invalid index for entry with size " + size); } /// <summary> /// Returns byte of tag value at offset </summary> /// <param name="index"> number of tag in entry (not in entire FIX message) </param> /// <returns> byte of tag value </returns> public virtual byte GetTagValueAsByteAtIndex(int index, int offset) { var size = Count; if (index < size && index >= 0) { var entry = EntriesArray.GetEntry(EntryIndex); return Storage.GetTagValueAsByteAtIndex(EntriesArray.GetTagLinkAtIndex(entry, index), offset); } throw new IndexOutOfRangeException("Invalid index for entry with size " + size); } /// <summary> /// Returns tag value as boolean </summary> /// <param name="index"> number of tag in entry (not in entire FIX message) </param> /// <returns> tag value </returns> public virtual bool GetTagValueAsBoolAtIndex(int index) { var size = Count; if (index < size && index >= 0) { var entry = EntriesArray.GetEntry(EntryIndex); return Storage.GetTagValueAsBoolAtIndex(EntriesArray.GetTagLinkAtIndex(entry, index)); } throw new IndexOutOfRangeException("Invalid index for entry with size " + size); } /// <summary> /// Returns tag value as double </summary> /// <param name="index"> number of tag in entry (not in entire FIX message) </param> /// <returns> tag value </returns> public virtual double GetTagValueAsDoubleAtIndex(int index) { var size = Count; if (index < size && index >= 0) { var entry = EntriesArray.GetEntry(EntryIndex); return Storage.GetTagValueAsDoubleAtIndex(EntriesArray.GetTagLinkAtIndex(entry, index)); } throw new IndexOutOfRangeException("Invalid index for entry with size " + size); } /// <summary> /// Returns tag value as long </summary> /// <param name="index"> number of tag in entry (not in entire FIX message) </param> /// <returns> tag value </returns> public virtual long GetTagValueAsLongAtIndex(int index) { var size = Count; if (index < size && index >= 0) { var entry = EntriesArray.GetEntry(EntryIndex); return Storage.GetTagValueAsLongAtIndex(EntriesArray.GetTagLinkAtIndex(entry, index)); } throw new IndexOutOfRangeException("Invalid index for entry with size " + size); } /// <summary> /// Fills passed StringBuffer by tag value </summary> /// <param name="index"> number of tag in entry (not in entire FIX message) </param> /// <param name="str"> buffer for filled </param> public virtual void GetTagValueAsStringBuffAtIndex(int index, StringBuilder str) { var size = Count; if (index < size && index >= 0) { var entry = EntriesArray.GetEntry(EntryIndex); Storage.GetTagValueAsStringBuffAtIndex(EntriesArray.GetTagLinkAtIndex(entry, index), str); return; } throw new IndexOutOfRangeException("Invalid index for entry with size " + size); } /// <summary> /// Returns repeating group </summary> /// <param name="index"> number of leading tag of group in entry (not in entire FIX message) </param> /// <returns> instance of RepeatingGroup from RepeatingGroupPool. There is no need to call release for this object. </returns> public virtual RepeatingGroup GetRepeatingGroupAtIndex(int index) { var group = RepeatingGroupPool.RepeatingGroup; GetRepeatingGroupAtIndex(index, group); return group; } /// <summary> /// Fills repeating group by repeating group data </summary> /// <param name="index"> number of leading tag of group in entry (not in entire FIX message) </param> /// <param name="group"> repeating group object for fill </param> public virtual void GetRepeatingGroupAtIndex(int index, RepeatingGroup group) { var size = Count; if (index < size && index >= 0) { var entry = EntriesArray.GetEntry(EntryIndex); var tag = EntriesArray.GetTagAtIndex(entry, index); var rgId = EntriesArray.GetTagLinkAtIndex(entry, index); RgStorage.GetRepeatingGroup(tag, rgId, group); return; } throw new IndexOutOfRangeException("Invalid index for entry with size " + size); } /// <summary> /// Removes tag from entry </summary> /// <param name="tag"> tag for remove </param> /// <returns> is tag was removes </returns> public virtual bool RemoveTag(int tag) { var entry = EntriesArray.GetEntry(EntryIndex); for (var i = EntriesHeaderSize; i < EntriesArray.GetArrayEnd(entry); i += EntriesEntrySize) { if (entry[i + EntriesTag] == tag) { RemoveTag(entry, i); return true; } } return false; } /// <summary> /// Removes tag from entry by index </summary> /// <param name="index"> number of tag in entry (not in entire FIX message) </param> /// <returns> is tag was removes </returns> public virtual bool RemoveTagAtIndex(int index) { var size = Count; if (index < size && index >= 0) { var entry = EntriesArray.GetEntry(EntryIndex); var indexInEntry = EntriesHeaderSize + index * EntriesEntrySize; RemoveTag(entry, indexInEntry); return true; } return false; } /// <summary> /// Removes repeating group from entry </summary> /// <param name="leadingTag"> leading tag of repeating group </param> /// <returns> true if repeating group was deleted </returns> public virtual bool RemoveRepeatingGroup(int leadingTag) { if (IsRepeatingGroupExists(leadingTag)) { if (IsGroupTag(leadingTag)) { return RemoveTag(leadingTag); } return RemoveHidedRepeatingGroup(leadingTag); } return false; } private bool RemoveHidedRepeatingGroup(int leadingTag) { return _hiddenLeadingTagsArray.RemoveFromHidedTagsByLeadingTagAndEntry(leadingTag, EntryIndex); } /// <summary> /// Removes repeating group from entry </summary> /// <param name="index"> index of leading tag group in entry (not in entire FIX message) </param> /// <returns> true if repeating group was deleted </returns> public virtual bool RemoveRepeatingGroupAtIndex(int index) { return RemoveTagAtIndex(Storage.GetTagIdAtIndex(index)); } private void RemoveTag(int[] entry, int index) { if (EntriesArray.GetEntryType(entry, index) == LinkTypeTag) { RgStorage.RemoveRgTagAtIndex(EntriesArray.GetEntryLink(entry, index), EntriesArray.GetParentEntryLink(entry)); RemoveTagFromEntry(entry, index); if (EntriesArray.IsEmpty(entry)) { RgStorage.DecrementLeadingTag(Group.LeadingTag, Group.RgId, 0, EntriesArray.GetParentEntryLink(entry)); //remove repeating group from fix message } } else if (EntriesArray.GetEntryType(entry, index) == LinkTypeRg) { var group = RepeatingGroupPool.RepeatingGroup; RgStorage.GetRepeatingGroup(EntriesArray.GetEntryTag(entry, index), EntriesArray.GetEntryLink(entry, index), group); group.Remove(); group.Release(); } } private void RemoveTagFromEntry(int[] entry, int index) { EntriesArray.RemoveTagAtIndex(entry, index); Group.UpdateParentEntries(entry, RgStorage.Entries, EntriesArray.GetLastTagIndexInFixMessage(entry) + 1, -1); } /// <summary> /// Returns repeating group from entry by leading tag. If group doesn't exist, it will be added. </summary> /// <param name="leadingTag"> leading tag for repeating group </param> /// <returns> instance of RepeatingGroup from RepeatingGroupPool. There is no need to call release for this object. </returns> public virtual RepeatingGroup GetOrAddRepeatingGroup(int leadingTag) { if (IsRepeatingGroupExists(leadingTag)) { return GetRepeatingGroup(leadingTag); } return AddRepeatingGroup(leadingTag); } /// <summary> /// Fills passed repeating group instance by data from entry. If group doesn't exist, it will be added. </summary> /// <param name="leadingTag"> leading tag for repeating group </param> /// <param name="group"> repeating group object for fill </param> public virtual void GetOrAddRepeatingGroup(int leadingTag, RepeatingGroup group) { if (IsRepeatingGroupExists(leadingTag)) { GetRepeatingGroup(leadingTag, group); } else { AddRepeatingGroup(leadingTag, group); } } /// <summary> /// Checks is entry contains nested group with passed leading tag. /// Note that empty groups, that doesn't appear in the message, also considered existing </summary> /// <param name="leadingTag"> leading tag for check </param> /// <returns> true if repeating group with passed leading tag exists. </returns> public virtual bool IsRepeatingGroupExists(int leadingTag) { var isShowedGroup = IsGroupTag(leadingTag); if (!isShowedGroup) { return _hiddenLeadingTagsArray.FindInHidedLeadingTagsByEntryOwner(leadingTag, EntryIndex) != -1; } return isShowedGroup; } /// <summary> /// Checks whether tag is leading tag of group </summary> /// <param name="tag"> tag for check </param> /// <returns> true if tag is leading tag of group, false if tag doesn't exists or is not leading tag of group </returns> public virtual bool IsGroupTag(int tag) { var entry = EntriesArray.GetEntry(EntryIndex); for (var i = EntriesHeaderSize; i < EntriesArray.GetArrayEnd(entry); i += EntriesEntrySize) { if (EntriesArray.GetEntryTag(entry, i) == tag) { return EntriesArray.GetEntryType(entry, i) == LinkTypeRg; } } return false; } /// <summary> /// Checks whether tag is group tag at index </summary> /// <param name="index"> number of tag in entry (not in entire FIX message) </param> /// <returns> true if tag is group tag </returns> public virtual bool IsGroupTagAtIndex(int index) { var size = Count; if (index < size && index >= 0) { var entry = EntriesArray.GetEntry(EntryIndex); return EntriesArray.GetTagTypeAtIndex(entry, index) == LinkTypeRg; } throw new IndexOutOfRangeException("Invalid index for entry with size " + size); } public virtual int GetTagIndex(int tag) { return FindTagLink(tag); } private int FindTagLink(int tag) { if (Deleted) { throw new InvalidOperationException("Entry was deleted. You should create new entry"); } var entry = EntriesArray.GetEntry(EntryIndex); for (var i = EntriesHeaderSize; i < EntriesArray.GetArrayEnd(entry); i += EntriesEntrySize) { if (EntriesArray.GetEntryTag(entry, i) == tag) { if (EntriesArray.GetEntryType(entry, i) == LinkTypeTag) { return EntriesArray.GetEntryLink(entry, i); } var repeatingGroup = RgArray.GetRgArrayById(tag, EntriesArray.GetEntryLink(entry, i)); if (repeatingGroup == null) { return -1; } return RgArray.GetRgLeadingTagIndexInFixMsg(repeatingGroup); } } return -1; } public virtual void Clear() { for (var i = 0; i < AllocatedSubGroups.Count; i++) { RepeatingGroupPool.ReturnObj(AllocatedSubGroups[i]); } AllocatedSubGroups.Clear(); ReleaseNeeded = true; Storage = null; Group = null; EntryIndex = -1; Deleted = false; GroupTags = null; _hiddenLeadingTagsArray = null; EntriesArray = null; RgArray = null; OuterLeadingTags = null; } /// <summary> /// Returns repeating group from entry by leading tag </summary> /// <param name="leadingTag"> leading tag for repeating group </param> /// <returns> instance of RepeatingGroup from RepeatingGroupPool. There is no need to call release for this object. </returns> public virtual RepeatingGroup GetRepeatingGroup(int leadingTag) { RepeatingGroup group = null; for (var i = 0; i < AllocatedSubGroups.Count; i++) { group = AllocatedSubGroups[i]; if (group.LeadingTag == leadingTag) { break; } } if (group == null || group.LeadingTag != leadingTag) { group = RepeatingGroupPool.RepeatingGroup; @group.ReleaseNeeded = false; AllocatedSubGroups.Add(group); } return InitRepeatingGroup(leadingTag, group); } /// <summary> /// Fills passed repeating group instance by data from entry </summary> /// <param name="leadingTag"> leading tag for repeating group </param> /// <param name="group"> repeating group object for fill </param> public virtual void GetRepeatingGroup(int leadingTag, RepeatingGroup group) { group = InitRepeatingGroup(leadingTag, group); if (group == null) { throw new FieldNotFoundException("There is no Repeating Group with tag = " + leadingTag); } } private RepeatingGroup InitRepeatingGroup(int leadingTag, RepeatingGroup group) { var rgIndex = _hiddenLeadingTagsArray.FindInHidedLeadingTagsByEntryOwner(leadingTag, EntryIndex); if (rgIndex == -1) { var entry = RgStorage.Entries[EntryIndex]; for (var i = EntriesHeaderSize; i < EntriesArray.GetArrayEnd(entry); i += EntriesEntrySize) { if (EntriesArray.GetEntryTag(entry, i) == leadingTag && EntriesArray.GetEntryType(entry, i) == LinkTypeRg) { RgStorage.GetRepeatingGroup(leadingTag, EntriesArray.GetEntryLink(entry, i), group); return group; } } return null; } var rgId = _hiddenLeadingTagsArray.GetRgId(rgIndex); RgStorage.GetRepeatingGroup(leadingTag, rgId, group); return group; } private void IsTagValid(int tag) { if (!GroupTags.Contains(tag)) { throw new UnresolvedGroupTagException(tag, Group.LeadingTag, Group.Version, Group.MsgType); } } private void IsLeadingTagValid(int leadingTag) { if (!OuterLeadingTags.Contains(leadingTag) && !NestedLeadingTags.Contains(leadingTag)) { throw new InvalidLeadingTagException(leadingTag, Group.Version, Group.MsgType); } } private void IsDuplicateTag(int tag) { var entry = EntriesArray.GetEntry(EntryIndex); for (var i = EntriesHeaderSize; i < EntriesArray.GetArrayEnd(entry); i += EntriesEntrySize) { if (EntriesArray.GetEntryTag(entry, i) == tag) { throw new DuplicateTagException(Group.LeadingTag, tag, Group.Version, Group.MsgType); } } } private void IsDuplicateGroup(int leadingTag) { var entry = EntriesArray.GetEntry(EntryIndex); for (var i = EntriesHeaderSize; i < EntriesArray.GetArrayEnd(entry); i += EntriesEntrySize) { if (EntriesArray.GetEntryTag(entry, i) == leadingTag) { throw new DuplicateGroupException(leadingTag, Group.Version, Group.MsgType); } } for (var i = HidedHeaderSize; i < _hiddenLeadingTagsArray.ArrayEnd; i += HidedEntrySize) { if (_hiddenLeadingTagsArray.GetTag(i) == leadingTag && _hiddenLeadingTagsArray.GetEntryLink(i) == EntryIndex) { throw new DuplicateGroupException(leadingTag, Group.Version, Group.MsgType); } } } internal virtual void Init(RepeatingGroup group, int entryIndex, IndexedStorage storage) { Group = group; Storage = storage; EntryIndex = entryIndex; RgStorage = @group.RepeatingGroupStorage; RgArray = @group.RepeatingGroupsArray; EntriesArray = @group.EntriesArray; GroupTags = group.Dict.GetGroupTags(group.LeadingTag); OuterLeadingTags = group.Dict.GetOuterLeadingTags(); NestedLeadingTags = group.Dict.GetNestedLeadingTags(); _hiddenLeadingTagsArray = group.HiddenLeadingTagsArray; Deleted = false; } public override string ToString() { var entry = EntriesArray.GetEntry(EntryIndex); if (entry != null) { return StringHelper.NewString(AsByteArray()); } return ""; } public virtual string ToPrintableString() { var entry = EntriesArray.GetEntry(EntryIndex); if (entry != null) { return FixMessagePrintableFormatter.ToPrintableString(ToString()); } return ""; } /// <summary> /// Returns number of added tag in entry. /// </summary> public virtual int Count { get { if (Deleted) { return 0; } var entry = EntriesArray.GetEntry(EntryIndex); return (EntriesArray.GetArrayEnd(entry) - EntriesHeaderSize) / EntriesEntrySize; } } /// <summary> /// Returns group which owns entry </summary> /// <returns> group which owns entry </returns> public virtual RepeatingGroup GetGroup() { return Group; } public virtual bool IsEmpty => Count <= 0; /// <summary> /// Returns entry index in FIX message </summary> /// <returns> entry index in FIX message </returns> public virtual int GetEntryIndex() { return Group.GetStartTagLink(EntriesArray.GetEntry(EntryIndex)); } public virtual byte[] AsByteArray() { var entry = EntriesArray.GetEntry(EntryIndex); var entryTagsSize = Group.GetEntryTagsSize(entry); var result = new byte[entryTagsSize]; if (result.Length == 0) { return result; } var startIndex = Group.GetStartTagLink(entry); var endIndex = EntriesArray.GetLastTagIndexInFixMessage(entry); var offset = 0; var msgSize = Storage.Count; for (var i = startIndex; i <= endIndex && i < msgSize; i++) { offset = Group.WriteTag(result, Storage.GetTagIdAtIndex(i), offset); offset = Group.WriteValue(result, Storage.GetTagValueAsBytesAtIndex(i), offset); } return result; } /// <summary> /// Copy repeating group to entry </summary> /// <param name="source"> repeating group for copy </param> /// <returns> copied repeating group </returns> public virtual RepeatingGroup CopyRepeatingGroup(RepeatingGroup source) { var dst = AddRepeatingGroup(source.LeadingTag, source.Validation); for (var i = 0; i < source.Count; i++) { dst.CopyEntry(source.GetEntry(i)); } return dst; } /// <summary> /// Copy repeating group to entry </summary> /// <param name="source"> repeating group for copy </param> /// <param name="dst"> entry for hold copied repeating group </param> public virtual void CopyRepeatingGroup(RepeatingGroup source, RepeatingGroup dst) { AddRepeatingGroup(source.LeadingTag, source.Validation, dst); for (var i = 0; i < source.Count; i++) { dst.CopyEntry(source.GetEntry(i)); } } } }