FixAntenna/NetCore/Message/Rg/RepeatingGroupStorage.cs (1,103 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.Common; using Epam.FixAntenna.NetCore.Configuration; using Epam.FixAntenna.NetCore.Message.Rg.Exceptions; namespace Epam.FixAntenna.NetCore.Message.Rg { internal sealed class RepeatingGroupStorage { //32 is maximum value, 8 is minimum value public const int InitialSize = 8; public const int RgHashTag = 0; //leading tag of group public const int RgHashId = 1; //unique id, used to distinguish groups with the same leading tag public const int RgHashTagLink = 2; //link to leading tag at indexed storage public const int RgHashLastEntryPointer = 3; //pointer at end of repeating group public const int RgHashParentEntryLink = 4; //link to parent entry public const int RgHashVirtualTagLink = 5; //virtual tag link from hidden leading tags array public const int RgHashHeaderSize = 6; public const int RgHashEntryLinkIndex = 0; //pointer at entry in entries array public const int RgHashEntrySize = 1; public const int EntriesLastTagPointerIndex = 0; //pointer at last tag at entry array public const int EntriesLastTagLinkIndex = 1; //link to last tag at storage public const int EntriesParentEntryLink = 2; public const int EntriesHeaderSize = 3; public const int EntriesTag = 0; public const int EntriesLink = 1; public const int EntriesType = 2; public const int EntriesEntrySize = 3; public const int HidedHeaderArrayEnd = 0; //link to last entry in hidden leading tags arrays public const int HidedHeaderSize = 1; public const int HidedTag = 0; //hidden leading tag value public const int HidedTagLinkIndex = 1; //hidden leading tag index in fix message. Leading tag will be added at this index. public const int HidedRgId = 2; //unique id for leading tag for distinguish few tags with same leading tag public const int HidedEntry = 3; //entry, who owns group's leading tag, -1 if group at the top level public const int HidedTagLinkVirtual = 4; //virtual leading tag link. Used when few groups added at same index. public const int HidedEntrySize = 5; public const int LinkTypeTag = 0; public const int LinkTypeRg = 1; private readonly IList<RepeatingGroup> _allocatedGroups = new List<RepeatingGroup>(); private int _currentEntry; /// <summary> /// Contains info about entries in format: /// Header: /// 1. Pointer at end of entry /// 2. Maximum tag index in fieldIndex for current entry /// Entry: /// 1. Tag number /// 2. Tag index in fieldIndex or in repeating group array /// 3. Type of Link: /// 1 = Tag in field index /// 2 = Tag in Repeating Group array /// </summary> private int[][] _entries; private EntriesArray _entriesArray; private bool _entryCreating; /// <summary> /// Array with leading tags that doesn't inserted into fix message because have value=0. /// It is possible in cases when group just created and entries not yet added and when all entries deleted from the group, but the group is not deleted. /// Tags should be removed from this array when leading tag inserts into message or when group is deleted. /// </summary> private int[] _hiddenLeadingTags; private HiddenLeadingTagsArray _hiddenLeadingTagsArray; private string _msgType; /// <summary> /// Contains info about Repeating Groups in format: /// Header: /// 1. Leading tag /// 2. Unique id for distinguish groups with same leading tag /// 3. Link to leading tag at indexed storage /// 4. Pointer at end of Repeating Group /// 5. Link to parent entry /// Entry: /// 1. Pointer to entry in entries array /// </summary> private int[][] _rgArray; private RepeatingGroupArray _rgArrayManager; private int _rgCount, _lastEntryPointer; private bool _rgCreating; private int _rgId; //Source of unique id private Stash _stash = new Stash(); private IndexedStorage _storage; private bool _validation; private FixVersionContainer _version; public RepeatingGroupStorage(IndexedStorage storage, FixVersion version, string msgType, bool validation) : this(storage, FixVersionContainer.GetFixVersionContainer(version), msgType, validation) { } public RepeatingGroupStorage(IndexedStorage storage, FixVersionContainer version, string msgType, bool validation) { _storage = storage; _version = version; _msgType = msgType; _validation = validation; _rgArray = RepeatingGroupStorageIntArrayPool.GetTwoDimIntArrayFromPool(InitialSize); _entries = RepeatingGroupStorageIntArrayPool.GetTwoDimIntArrayFromPool(InitialSize); _hiddenLeadingTags = RepeatingGroupStorageIntArrayPool.GetIntArrayFromPool(InitialSize); _hiddenLeadingTags[HidedHeaderArrayEnd] = HidedHeaderSize; _currentEntry = -1; _lastEntryPointer = -1; _rgArrayManager = new RepeatingGroupArray(_rgArray); _entriesArray = new EntriesArray(_entries); //todo - prevent allocation _hiddenLeadingTagsArray = new HiddenLeadingTagsArray(_hiddenLeadingTags); } private RepeatingGroupStorage() { } public void Init(bool validation) { if (_version != null && !ReferenceEquals(_msgType, null)) { Init(_version, _msgType, validation); } throw new InvalidOperationException( "Repeating Group storage is not initialized properly. You should call RawFixUtil.indexRepeatingGroup() method before first call addRepeatingGroupAtIndex"); } public void Init(FixVersion version, string msgType, bool validation) { Init(FixVersionContainer.GetFixVersionContainer(version), msgType, validation); } public void Init(FixVersionContainer version, string msgType, bool validation) { _rgArray = RepeatingGroupStorageIntArrayPool.GetTwoDimIntArrayFromPool(InitialSize); _entries = RepeatingGroupStorageIntArrayPool.GetTwoDimIntArrayFromPool(InitialSize); _hiddenLeadingTags = RepeatingGroupStorageIntArrayPool.GetIntArrayFromPool(InitialSize); _hiddenLeadingTags[HidedHeaderArrayEnd] = HidedHeaderSize; _currentEntry = -1; _lastEntryPointer = -1; _version = version; _msgType = msgType; _validation = validation; IsInvalidated = false; _rgArrayManager = new RepeatingGroupArray(_rgArray); _entriesArray = new EntriesArray(_entries); //todo - prevent allocation _hiddenLeadingTagsArray = new HiddenLeadingTagsArray(_hiddenLeadingTags); } public void ClearRepeatingGroupStorage() { if (!IsInvalidated) { if (_rgArray != null) { for (var i = 0; i < _rgArray.Length; i++) { if (_rgArray[i] != null) { RepeatingGroupStorageIntArrayPool.ReturnObj(_rgArray[i]); } } RepeatingGroupStorageIntArrayPool.ReturnObj(_rgArray); _rgArray = null; if (_rgArrayManager != null) { _rgArrayManager.SetRgArray(null); } } if (_entries != null) { int[] entry; for (var i = 0; i < _entries.Length && (entry = _entries[i]) != null; i++) { RepeatingGroupStorageIntArrayPool.ReturnObj(entry); } RepeatingGroupStorageIntArrayPool.ReturnObj(_entries); if (_entriesArray != null) { _entriesArray.SetEntries(null); } } for (var i = 0; i < _allocatedGroups.Count; i++) { RepeatingGroupPool.ReturnObj(_allocatedGroups[i]); } if (_hiddenLeadingTags != null) { RepeatingGroupStorageIntArrayPool.ReturnObj(_hiddenLeadingTags); _hiddenLeadingTags = null; _hiddenLeadingTagsArray.SetHiddenLeadingTags(null); } _allocatedGroups.Clear(); _entries = null; _stash.Clear(); _rgCount = 0; _lastEntryPointer = -1; _entryCreating = false; _rgId = 0; _currentEntry = -1; _rgCreating = false; IsInvalidated = true; } } //Methods for first indexing public void StartCreateRg(int leadingTag, int leadingTagIndex, int size, int delimTag) { EnsureEntriesCapacityAndEnlarge(); var startEntry = _lastEntryPointer + 1; var endEntry = startEntry + size - 1; int rgIndex; if (_rgCreating) { rgIndex = AddRgToArray(leadingTagIndex, leadingTag, -1, _currentEntry); AddTagToEntry(leadingTag, _rgArrayManager.GetRgId(rgIndex), LinkTypeRg); _currentEntry = _lastEntryPointer; } else { if (_validation) { ValidateGroupDuplicate(leadingTag); } rgIndex = AddRgToArray(leadingTagIndex, leadingTag, -1, -1); } _rgArrayManager.SetRgLeadingTagIndexInFixMsg(rgIndex, leadingTagIndex); _rgArrayManager.SetZeroSize(rgIndex); _stash.StashValue(delimTag, rgIndex); //Create all entries for group for (var i = startEntry; i <= endEntry; i++) { EnsureRgCapacityAndEnlarge(rgIndex); EnsureEntriesCapacityAndEnlarge(); _rgArrayManager.AddEntry(rgIndex, i); _entries[i] = RepeatingGroupStorageIntArrayPool.GetIntArrayFromPool(InitialSize); var entry = _entries[i]; _entriesArray.SetZeroSize(entry); //mark entry as unfinished entry[_entriesArray.GetArrayEnd(_entries[i])] = -1; if (_rgCreating) { _entriesArray.SetParentEntryLink(entry, _rgArrayManager.GetParentEntryIndex(rgIndex)); } else { _entriesArray.SetParentEntryLink(entry, -1); } } _lastEntryPointer += size; _entryCreating = false; _rgCreating = true; } public void StopCreateRg() { if (_stash.HasRgStash()) { FinishEntry(); ValidateRgEntries(); var repeatingGroup = _rgArray[_stash.GetRgPointer()]; var lastEntry = _entries[_rgArrayManager.GetLastEntryLink(repeatingGroup)]; var lastTagLink = _entriesArray.GetLastTagIndexInFixMessage(lastEntry); _stash.Unstash(); var currentRgPointer = _stash.GetRgPointer(); //Find next unfinished entry repeatingGroup = _rgArray[currentRgPointer]; for (var i = RgHashHeaderSize; i < _rgArrayManager.GetRgArrayEnd(repeatingGroup); i += RgHashEntrySize) { var entryPointer = _rgArrayManager.GetEntryLink(repeatingGroup, i); var entry = _entries[entryPointer]; var lastTagPointer = _entriesArray.GetArrayEnd(entry); if (entry[lastTagPointer] == -1) { _currentEntry = entryPointer; if (_entriesArray.GetLastTagIndexInFixMessage(entry) < lastTagLink) { _entriesArray.SetLastTagIndexInFixMessage(entry, lastTagLink); } break; } } } else { _rgCreating = false; FinishEntry(); ValidateRgEntries(); _stash.Clear(); _currentEntry = _lastEntryPointer; } } public void AddTag(int tag, int tagIndex, int counterTag) { if (tag == _stash.GetDelimTag()) { if (_entryCreating) { FinishEntry(); } StartEntry(counterTag); } AddTagToEntry(tag, tagIndex, LinkTypeTag); } private void AddTagToEntry(int tag, int tagIndex, int type) { var entry = EnsureEntryCapacityAndEnlarge(_currentEntry); ValidateEntry(tag, entry); if (type == LinkTypeTag) { if (tagIndex > _entriesArray.GetLastTagIndexInFixMessage(entry)) { _entriesArray.SetLastTagIndexInFixMessage(entry, tagIndex); } } _entriesArray.AddEntry(entry, tag, tagIndex, type); //Mark last entry as unfinished entry[_entriesArray.GetArrayEnd(entry)] = -1; } /// <summary> /// Start creating entry. /// </summary> /// <param name="counterTag"> counter tag </param> /// <exception cref="InvalidLeadingTagValueException"> if there is invalid counter tag value in the specified message. </exception> private void StartEntry(int counterTag) { EnsureEntriesCapacityAndEnlarge(); _entryCreating = true; _currentEntry++; var entry = _entries[_currentEntry]; if (entry == null) { throw new InvalidLeadingTagValueException(counterTag, false, _version, _msgType); } ValidateLeadingTagValueLess(entry); _entriesArray.SetZeroSize(entry); } private void FinishEntry() { var endEntryPointer = _entriesArray.GetArrayEnd(_entries[_currentEntry]); _entries[_currentEntry][endEntryPointer] = 0; } //END methods for first indexing //Start methods for validation public void ValidateLeadingTag(int leadingTag) { if (!DictionaryHolder.GetDictionary(_version).GetMessageDict(_msgType).GetOuterLeadingTags() .Contains(leadingTag)) { throw new InvalidLeadingTagException(leadingTag, _version, _msgType); } } public void ValidateGroupDuplicate(int leadingTag) { var indexInHided = _hiddenLeadingTagsArray.FindInHidedLeadingTags(leadingTag, -1); var indexInRgArray = _rgArrayManager.FindRgIndex(leadingTag); if (indexInHided != -1 || indexInRgArray != -1) { throw new DuplicateGroupException(leadingTag, _version, _msgType); } } private void ValidateRgEntries() { if (_validation) { var repeatingGroup = _rgArray[_stash.GetRgPointer()]; for (var i = RgHashHeaderSize; i < _rgArrayManager.GetRgArrayEnd(repeatingGroup); i += RgHashEntrySize) { var entry = _entries[_rgArrayManager.GetEntryLink(repeatingGroup, i)]; if (entry[_entriesArray.GetArrayEnd(entry)] == -1) { throw new InvalidLeadingTagValueException(_rgArrayManager.GetRgLeadingTag(repeatingGroup), true, _version, _msgType); } } } } private void ValidateLeadingTagValueLess(int[] entry) { if (_validation) { if (entry == null || entry[EntriesHeaderSize] != -1) { var repeatingGroup = _rgArray[_stash.GetRgPointer()]; throw new InvalidLeadingTagValueException(_rgArrayManager.GetRgLeadingTag(repeatingGroup), false, _version, _msgType); } } } private void ValidateEntry(int tag, int[] entry) { if (_validation) { for (var i = EntriesHeaderSize; i < _entriesArray.GetArrayEnd(entry); i += EntriesEntrySize) { if (_entriesArray.GetEntryTag(entry, i) == tag) { throw new DuplicateTagException(_rgArrayManager.GetRgLeadingTag(_stash.GetRgPointer()), tag, _version, _msgType); } } } } //End methods for validation internal void IncrementLeadingTag(int newEntriesCount, int leadingTag, int rgId, int parentEntryIndex, int updatedEntryIndex, int rgIndex) { if (newEntriesCount == 1) { var indexAtHidedTag = _hiddenLeadingTagsArray.FindInHidedLeadingTags(leadingTag, rgId); var leadingTagIndexInFixMsg = FindRealLeadingTagIndex( _hiddenLeadingTagsArray.GetTagLink(indexAtHidedTag), _hiddenLeadingTagsArray.GetTagLinkVirtual(indexAtHidedTag)); var parentEntry = _hiddenLeadingTagsArray.GetEntryLink(indexAtHidedTag); _storage.AddTagAtIndex(leadingTagIndexInFixMsg, leadingTag, newEntriesCount, false); Shift(leadingTagIndexInFixMsg, 1, parentEntryIndex, updatedEntryIndex, false); if (parentEntry != -1) { //If there is parent entry, should insert leading tag in this entry var entry = EnsureEntryCapacityAndEnlarge(parentEntry); var indexAtEntryArray = FindIndexAtEntryArray(leadingTagIndexInFixMsg, entry); _entriesArray.AddEntryAtIndex(entry, indexAtEntryArray, leadingTag, rgId, LinkTypeRg); UpdateParentEntries(parentEntry, leadingTagIndexInFixMsg, 1); } FillRg(rgIndex, leadingTagIndexInFixMsg, leadingTag, _hiddenLeadingTagsArray.GetTagLinkVirtual(indexAtHidedTag), rgId, parentEntry); _hiddenLeadingTagsArray.RemoveFromHidedTags(leadingTag, rgId); } else { _storage.UpdateValueAtIndex(_rgArrayManager.GetRgLeadingTagIndexById(leadingTag, rgId), newEntriesCount); } } private int FindIndexAtEntryArray(int leadingTagIndexInFixMsg, int[] entry) { var indexAtEntryArray = EntriesHeaderSize; for (; indexAtEntryArray < _entriesArray.GetArrayEnd(entry); indexAtEntryArray += EntriesEntrySize) { int tagIndexInFixMsg; if (_entriesArray.GetEntryType(entry, indexAtEntryArray) == LinkTypeTag) { tagIndexInFixMsg = _entriesArray.GetEntryLink(entry, indexAtEntryArray); } else { var subRgIndex = _entriesArray.GetEntryLink(entry, indexAtEntryArray); tagIndexInFixMsg = _rgArrayManager.GetRgLeadingTagIndexInFixMsg(subRgIndex); } if (tagIndexInFixMsg >= leadingTagIndexInFixMsg) { break; } } return indexAtEntryArray; } internal void DecrementLeadingTag(int leadingTag, int rgId, int newEntriesCount, int parentEntryIndex) { if (newEntriesCount == 0) { var rgIndex = _rgArrayManager.FindRgIndex(leadingTag, rgId); var repeatingGroup = RepeatingGroups[rgIndex]; var leadingTagIndexInFixMsg = _rgArrayManager.GetRgLeadingTagIndexInFixMsg(repeatingGroup); //Move tag from repeating group array to hidden tags AddToHidedTags(leadingTag, leadingTagIndexInFixMsg, rgId, parentEntryIndex, _rgArrayManager.GetVirtualLeadingTagIndex(repeatingGroup)); RemoveRepeatingGroup(rgIndex); //Remove leading tag from message Shift(leadingTagIndexInFixMsg, -1, parentEntryIndex, -1, false); _storage.RemoveTagAtIndex(leadingTagIndexInFixMsg, false); //if there is parent entry remove leading tag from this entry if (parentEntryIndex != -1) { var entry = Entries[parentEntryIndex]; for (var i = EntriesHeaderSize; i < _entriesArray.GetArrayEnd(entry); i += EntriesEntrySize) { if (_entriesArray.GetEntryTag(entry, i) == leadingTag && _entriesArray.GetEntryLink(entry, i) == rgId) { _entriesArray.RemoveTagAtIndex(entry, i); break; } } UpdateParentEntries(parentEntryIndex, leadingTagIndexInFixMsg, -1); if (_entriesArray.IsEmpty(parentEntryIndex)) { var parentRepeatingGroup = _rgArrayManager.GetRgArrayByEntryIndex(parentEntryIndex); var parentLeadingTag = _rgArrayManager.GetRgLeadingTag(parentRepeatingGroup); var parentRgId = _rgArrayManager.GetRgId(parentRepeatingGroup); var parentEntry = _rgArrayManager.GetParentEntryIndex(parentRepeatingGroup); var newSize = GetLeadingTagValue(parentLeadingTag, parentRgId) - 1; DecrementLeadingTag(parentLeadingTag, parentRgId, newSize, parentEntry); } } } else if (newEntriesCount > 0) { _storage.UpdateValueAtIndex(_rgArrayManager.GetRgLeadingTagIndexById(leadingTag, rgId), newEntriesCount); } } //Update links in all entries, starts from parentEntryIndex, if tagIndex internal void UpdateParentEntries(int parentEntryIndex, int insertedIndex, int offset) { while (parentEntryIndex != -1) { var parentEntry = _entriesArray.GetEntry(parentEntryIndex); _entriesArray.ShiftLastTagIndexInFixMessage(parentEntry, offset); var entryEnd = _entriesArray.GetArrayEnd(parentEntry); for (var j = EntriesHeaderSize; j < entryEnd; j += EntriesEntrySize) { if (_entriesArray.GetEntryLink(parentEntry, j) >= insertedIndex && _entriesArray.GetEntryType(parentEntry, j) == LinkTypeTag) { //don't update nested repeating groups, because they will be updated in shift method _entriesArray.ShiftEntryLink(parentEntry, j, offset); if (_entriesArray.GetLastTagIndexInFixMessage(parentEntry) < _entriesArray.GetEntryLink(parentEntry, j)) { //Update last link if needed _entriesArray.SetLastTagIndexInFixMessage(parentEntry, _entriesArray.GetEntryLink(parentEntry, j)); } } } //Move to next parent parentEntryIndex = _entriesArray.GetParentEntryLink(parentEntry); } } public void Shift(int index, int offset, int parentEntryIndex, int updatedEntry, bool shouldUpdatedHided) { int[] entry; for (var entryIndex = 0; entryIndex < _entries.Length && (entry = _entries[entryIndex]) != null; entryIndex++) { if (_entriesArray.GetLastTagIndexInFixMessage(entry) >= index) { //Shift the only ones right if (!IsNeedUpdate(parentEntryIndex, entryIndex)) { continue; } var entryEnd = _entriesArray.GetArrayEnd(entry); var updated = false; for (var j = EntriesHeaderSize; j < entryEnd; j += EntriesEntrySize) { if (_entriesArray.GetEntryLink(entry, j) >= index && _entriesArray.GetEntryType(entry, j) == LinkTypeTag) { _entriesArray.ShiftEntryLink(entry, j, offset); if (_entriesArray.GetLastTagIndexInFixMessage(entry) < _entriesArray.GetEntryLink(entry, j)) { //Update last link if needed _entriesArray.SetLastTagIndexInFixMessage(entry, _entriesArray.GetEntryLink(entry, j)); updated = true; } } } //Not updated but last tag index > index means that there is nested repeating group that affects last tag index, so index should be updated if (!updated && _entriesArray.GetLastTagIndexInFixMessage(entry) > index) { _entriesArray.ShiftLastTagIndexInFixMessage(entry, offset); } } } //update repeating group tags link for (var rgIndex = 0; rgIndex < _rgArray.Length; rgIndex++) { var rg = _rgArray[rgIndex]; if (rg != null && _rgArrayManager.GetRgId(rg) != -1) { if (_rgArrayManager.GetRgLeadingTagIndexInFixMsg(rg) >= index) { _rgArrayManager.AddRgLeadingTagIndexInFixMsg(rg, offset); } } } if (shouldUpdatedHided) { if (offset > 0) { for (var indexInHidedRg = HidedHeaderSize; indexInHidedRg < _hiddenLeadingTagsArray.ArrayEnd; indexInHidedRg += HidedEntrySize) { var needUpdate = true; var entryIndex = _hiddenLeadingTagsArray.GetEntryLink(indexInHidedRg); //if hidden group is nested if (entryIndex != -1) { //There is no need to update hidden leading tag index if hidden leading tag belongs to updated entry (or to any parent of updated entry) //It's because we don't support addRepeatingGroupAtIndex in Entry. So group should stay at place, where it was added needUpdate = IsNeedUpdateHidedTags(entryIndex, updatedEntry); } if (_hiddenLeadingTagsArray.GetTagLink(indexInHidedRg) >= index && _hiddenLeadingTagsArray.GetEntryLink(indexInHidedRg) != updatedEntry && needUpdate) { _hiddenLeadingTagsArray.ShiftHidedTagLink(indexInHidedRg, offset); } } } else { for (var indexInHidedRg = HidedHeaderSize; indexInHidedRg < _hiddenLeadingTagsArray.ArrayEnd; indexInHidedRg += HidedEntrySize) { if (_hiddenLeadingTagsArray.GetTagLink(indexInHidedRg) > index) { _hiddenLeadingTagsArray.ShiftHidedTagLink(indexInHidedRg, offset); } } } } } private bool IsNeedUpdate(int parentEntryIndex, int currentIndex) { //Don't update parent entries, because they will be updated in updateParentEntries var needUpdate = true; while (parentEntryIndex != -1) { if (parentEntryIndex == currentIndex) { needUpdate = false; break; } var parentEntry = _entries[parentEntryIndex]; parentEntryIndex = _entriesArray.GetParentEntryLink(parentEntry); } return needUpdate; } private bool IsNeedUpdateHidedTags(int hiddenEntryIndex, int updatedEntry) { var needUpdate = true; if (updatedEntry == -1) { return IsNeedUpdate(hiddenEntryIndex, updatedEntry); } var commonRgIndex = FindCommonAncestorGroup(hiddenEntryIndex, updatedEntry); //If entry, which owns hidden group, belongs to same group as updated entry if (commonRgIndex != -1) { //Find entry number in repeating group var hiddenEntryIndexInCommonGroup = FindEntryIndexInCommonGroup(hiddenEntryIndex, commonRgIndex); var updatedEntryIndexInCommonGroup = FindEntryIndexInCommonGroup(updatedEntry, commonRgIndex); //If hidden group leading tag in the same entry as updated tag or in entry, that lefter then updated entry (include all parents), then it should't updated if (hiddenEntryIndexInCommonGroup <= updatedEntryIndexInCommonGroup) { needUpdate = false; } } return needUpdate; } private int FindEntryIndexInCommonGroup(int entryIndex, int commonRgIndex) { var rgIndex = _rgArrayManager.GetRgIndexByEntryIndex(entryIndex); while (rgIndex != commonRgIndex) { entryIndex = _entriesArray.GetParentEntryLink(_entriesArray.GetEntry(entryIndex)); rgIndex = _rgArrayManager.GetRgIndexByEntryIndex(entryIndex); } return _rgArrayManager.GetIndexByEntryIndex(_rgArrayManager.GetRepeatingGroup(rgIndex), entryIndex); } private int FindCommonAncestorGroup(int entry1, int entry2) { var level1 = FindLevel(entry1); var level2 = FindLevel(entry2); if (level1 > level2) { entry1 = GoToLevel(entry1, level1, level2); } else if (level2 > level1) { entry2 = GoToLevel(entry2, level2, level1); } var entry1RgIndex = _rgArrayManager.GetRgIndexByEntryIndex(entry1); var entry2RgIndex = _rgArrayManager.GetRgIndexByEntryIndex(entry2); while (level1 >= 0) { if (entry1RgIndex == entry2RgIndex) { return entry1RgIndex; } level1--; entry1 = _entriesArray.GetParentEntryLink(_entriesArray.GetEntry(entry1)); entry2 = _entriesArray.GetParentEntryLink(_entriesArray.GetEntry(entry2)); entry1RgIndex = _rgArrayManager.GetRgIndexByEntryIndex(entry1); entry2RgIndex = _rgArrayManager.GetRgIndexByEntryIndex(entry2); } return -1; } private int GoToLevel(int entry, int startLevel, int targetLevel) { while (startLevel > targetLevel) { startLevel--; entry = _entriesArray.GetParentEntryLink(_entriesArray.GetEntry(entry)); } return entry; } private int FindLevel(int entryLink) { var level = 0; entryLink = _entriesArray.GetParentEntryLink(_entriesArray.GetEntry(entryLink)); while (entryLink != -1) { level++; entryLink = _entriesArray.GetParentEntryLink(_entriesArray.GetEntry(entryLink)); } return level; } public bool IsRepeatingGroupExists(int leadingTag) { var indexInHided = _hiddenLeadingTagsArray.FindInHidedLeadingTags(leadingTag, -1); if (indexInHided == -1) { return _rgArrayManager.FindRgIndex(leadingTag) != -1; } return true; } public void GetRepeatingGroup(int tag, RepeatingGroup group) { group = InitRepeatingGroup(tag, group); if (group == null) { throw new FieldNotFoundException("There is no repeating group with tag " + tag); } } public RepeatingGroup GetRepeatingGroup(int tag) { RepeatingGroup group = null; for (var i = 0; i < _allocatedGroups.Count; i++) { group = _allocatedGroups[i]; if (@group.LeadingTag == tag) { break; } } if (group == null || @group.LeadingTag != tag) { group = RepeatingGroupPool.RepeatingGroup; @group.ReleaseNeeded = false; _allocatedGroups.Add(group); } return InitRepeatingGroup(tag, group); } private RepeatingGroup InitRepeatingGroup(int tag, RepeatingGroup group) { var hidedRgIndex = _hiddenLeadingTagsArray.FindInHidedLeadingTags(tag, -1); int rgIndex, rgId, parentEntry; if (hidedRgIndex == -1) { rgIndex = _rgArrayManager.FindRgIndex(tag); if (rgIndex == -1 || _rgArray[rgIndex] == null) { return null; } rgId = _rgArrayManager.GetRgId(rgIndex); parentEntry = _rgArrayManager.GetParentEntryIndex(rgIndex); } else { rgId = _hiddenLeadingTagsArray.GetRgId(hidedRgIndex); rgIndex = _rgArrayManager.FindRgIndex(tag, rgId); parentEntry = _hiddenLeadingTagsArray.GetEntryLink(hidedRgIndex); } group.Init(tag, rgId, rgIndex, _storage, this, parentEntry, _version, _msgType); return group; } public void GetRepeatingGroup(int tag, int rgId, RepeatingGroup rg) { var rgIndex = _rgArrayManager.FindRgIndex(tag, rgId); int parentEntry; if (rgIndex == -1 || _rgArray[rgIndex] == null) { rgIndex = _hiddenLeadingTagsArray.FindInHidedLeadingTags(tag, rgId); if (rgIndex == -1) { throw new Exception("There is no rg with tag " + tag); } parentEntry = _hiddenLeadingTagsArray.GetEntryLink(rgIndex); } else { parentEntry = _rgArrayManager.GetParentEntryIndex(rgIndex); } rg.Init(tag, rgId, rgIndex, _storage, this, parentEntry, _version, _msgType); } public RepeatingGroupStorage Copy(IndexedStorage newStorage) { var copy = new RepeatingGroupStorage(); copy._rgCount = _rgCount; copy._lastEntryPointer = _lastEntryPointer; copy._entryCreating = _entryCreating; copy._rgId = _rgId; copy._currentEntry = _currentEntry; copy._rgCreating = _rgCreating; copy.IsInvalidated = IsInvalidated; copy._version = _version; copy._msgType = _msgType; copy._validation = _validation; copy._stash = new Stash(); copy._stash.Copy(_stash); copy._storage = newStorage; copy._rgArray = RepeatingGroupStorageIntArrayPool.GetTwoDimIntArrayFromPool(_rgArray.Length); copy._entries = RepeatingGroupStorageIntArrayPool.GetTwoDimIntArrayFromPool(_entries.Length); copy._hiddenLeadingTags = RepeatingGroupStorageIntArrayPool.GetIntArrayFromPool(_hiddenLeadingTags.Length); for (var rgIndex = 0; rgIndex < _rgArray.Length; rgIndex++) { if (_rgArray[rgIndex] != null) { copy._rgArray[rgIndex] = RepeatingGroupStorageIntArrayPool.GetIntArrayFromPool(_rgArray[rgIndex].Length); Array.Copy(_rgArray[rgIndex], 0, copy._rgArray[rgIndex], 0, _rgArray[rgIndex].Length); } } for (var entryIndex = 0; entryIndex < _entries.Length; entryIndex++) { if (_entries[entryIndex] != null) { copy._entries[entryIndex] = RepeatingGroupStorageIntArrayPool.GetIntArrayFromPool(_entries[entryIndex].Length); Array.Copy(_entries[entryIndex], 0, copy._entries[entryIndex], 0, _entries[entryIndex].Length); } } Array.Copy(_hiddenLeadingTags, 0, copy._hiddenLeadingTags, 0, _hiddenLeadingTags.Length); copy._hiddenLeadingTagsArray = new HiddenLeadingTagsArray(copy._hiddenLeadingTags); copy._rgArrayManager = new RepeatingGroupArray(copy._rgArray); copy._entriesArray = new EntriesArray(copy._entries); return copy; } internal int[][] RepeatingGroups => _rgArray; internal void RemoveRepeatingGroup(int rgIndex) { _rgArrayManager.SetRgId(rgIndex, -1); _rgArrayManager.SetRgLeadingTagIndexInFixMsg(rgIndex, -1); } internal int PrepareAdd(int index, int tagId, int parentEntry, int updatedEntry) { var fieldCount = _storage.ReserveTagAtIndex(index, tagId, false); // if (index != fieldCount - 1) { Shift(index, 1, parentEntry, updatedEntry, true); // } return fieldCount; } public RepeatingGroup AddRepeatingGroup(int indexInFixMessage, int leadingTag, bool validation, RepeatingGroup group) { EnsureRepeatingGroupArrayCapacityAndEnlarge(); EnsureEntriesCapacityAndEnlarge(); if (_currentEntry == -1) { _currentEntry = 0; } if (_entries[_currentEntry] != null) { _currentEntry++; } var parentEntryLink = -1; var rgId = _rgId++; if (indexInFixMessage == _storage.Count) { ShiftHidedLeadingTagsLeft(indexInFixMessage); } else { ShiftHidedLeadingTagsRight(indexInFixMessage); } AddToHidedTags(leadingTag, indexInFixMessage, rgId, -1); var rgIndex = AddRgToArray(-1, leadingTag, -1, -1, parentEntryLink); group.Init(leadingTag, rgId, rgIndex, _storage, this, parentEntryLink, _version, _msgType); @group.Validation = validation; @group.ReleaseNeeded = false; _allocatedGroups.Add(group); return group; } private void ShiftHidedLeadingTagsRight(int indexInFixMessage) { for (var i = HidedHeaderSize; i < _hiddenLeadingTagsArray.ArrayEnd; i += HidedEntrySize) { if (_hiddenLeadingTagsArray.GetTagLinkVirtual(i) >= indexInFixMessage) { _hiddenLeadingTagsArray.ShiftHidedVirtualTag(i, 1); } } } private void ShiftHidedLeadingTagsLeft(int indexInFixMessage) { for (var i = HidedHeaderSize; i < _hiddenLeadingTagsArray.ArrayEnd; i += HidedEntrySize) { if (_hiddenLeadingTagsArray.GetTagLinkVirtual(i) <= indexInFixMessage) { _hiddenLeadingTagsArray.ShiftHidedVirtualTag(i, -1); } } } internal int FindRealLeadingTagIndex(int desiredLeadingTagIndex, int virtualLeadingTagIndex) { /* We want insert group at desiredLeadingTagIndex. But some other groups already can be inserted at the same index. For example: msg.addRepeatingGroupAtIndex(1, 123); msg.addRepeatingGroupAtIndex(1, 456); In message 456 should be before 123. When we add group 456, leading tag index of group 123 was incremented. */ if (desiredLeadingTagIndex < _storage.Count) { var tagAtDesiredLeadingTagIndex = _storage.GetTagIdAtIndex(desiredLeadingTagIndex); var rgIndex = _rgArrayManager.FindRgIndex(tagAtDesiredLeadingTagIndex); var insertedVirtualLeadingTagIndex = -1; if (rgIndex != -1) { insertedVirtualLeadingTagIndex = _rgArrayManager.GetVirtualLeadingTagIndex(rgIndex); } //Iterate over groups, breaks when leading tag of inserted group became greater then leading tag of group for insert while (rgIndex != -1 && insertedVirtualLeadingTagIndex != -1 && insertedVirtualLeadingTagIndex < virtualLeadingTagIndex && desiredLeadingTagIndex < _storage.Count) { tagAtDesiredLeadingTagIndex = _storage.GetTagIdAtIndex(desiredLeadingTagIndex); rgIndex = _rgArrayManager.FindRgIndex(tagAtDesiredLeadingTagIndex); if (rgIndex != -1) { desiredLeadingTagIndex = _entriesArray.GetLastTagIndexInFixMessage( _entriesArray.GetEntry(_rgArrayManager.GetLastEntryLink(rgIndex))) + 1; insertedVirtualLeadingTagIndex = _rgArrayManager.GetVirtualLeadingTagIndex(rgIndex); } } } return desiredLeadingTagIndex; } public RepeatingGroup AddSubGroup(int indexInFixMessage, int leadingTag, int parentEntryIndex, RepeatingGroup group) { EnsureRepeatingGroupArrayCapacityAndEnlarge(); EnsureEntriesCapacityAndEnlarge(); if (_entries[_currentEntry] != null) { _currentEntry++; } var parentEntry = _entries[parentEntryIndex]; var rgId = _rgId++; ShiftHidedLeadingTagsLeft(indexInFixMessage); AddToHidedTags(leadingTag, indexInFixMessage, rgId, parentEntryIndex); var rgIndex = AddRgToArray(-1, leadingTag, -1, -1, parentEntryIndex); _entriesArray.SetEntryLink(parentEntry, _entriesArray.GetArrayEnd(parentEntry), rgId); group.Init(leadingTag, rgId, rgIndex, _storage, this, parentEntryIndex, _version, _msgType); return group; } internal int AddRgToArray(int indexInFixMessage, int leadingTag, int virtualLink, int parentEntryLink) { var rgIndex = _rgCount++; EnsureRepeatingGroupArrayCapacityAndEnlarge(); _rgArray[rgIndex] = RepeatingGroupStorageIntArrayPool.GetIntArrayFromPool(InitialSize); _rgArrayManager.SetRgLeadingTag(rgIndex, leadingTag); _rgArrayManager.SetZeroSize(rgIndex); _rgArrayManager.SetRgLeadingTagIndexInFixMsg(rgIndex, indexInFixMessage); _rgArrayManager.SetParentEntryIndex(rgIndex, parentEntryLink); _rgArrayManager.SetRgId(rgIndex, _rgId++); _rgArrayManager.SetVirtualLeadingTagIndex(rgIndex, virtualLink); return rgIndex; } internal int AddRgToArray(int indexInFixMessage, int leadingTag, int virtualLink, int rgId, int parentEntryLink) { var rgIndex = _rgCount++; EnsureRepeatingGroupArrayCapacityAndEnlarge(); _rgArray[rgIndex] = RepeatingGroupStorageIntArrayPool.GetIntArrayFromPool(InitialSize); _rgArrayManager.SetRgLeadingTag(rgIndex, leadingTag); _rgArrayManager.SetZeroSize(rgIndex); _rgArrayManager.SetRgLeadingTagIndexInFixMsg(rgIndex, indexInFixMessage); _rgArrayManager.SetParentEntryIndex(rgIndex, parentEntryLink); _rgArrayManager.SetRgId(rgIndex, rgId); _rgArrayManager.SetVirtualLeadingTagIndex(rgIndex, virtualLink); return rgIndex; } internal void ReAddRgToArray(int rgIndex, int rgId) { _rgArrayManager.SetRgId(rgIndex, rgId); } internal void FillRg(int rgIndex, int indexInFixMessage, int leadingTag, int virtualLink, int rgId, int parentEntryLink) { _rgArrayManager.SetRgLeadingTag(rgIndex, leadingTag); _rgArrayManager.SetRgLeadingTagIndexInFixMsg(rgIndex, indexInFixMessage); _rgArrayManager.SetParentEntryIndex(rgIndex, parentEntryLink); _rgArrayManager.SetRgId(rgIndex, rgId); _rgArrayManager.SetVirtualLeadingTagIndex(rgIndex, virtualLink); } internal void AddToHidedTags(int leadingTag, int indexInFixMessage, int rgId, int entryIndex) { EnsureHidedLeadingTagArrayCapacityAndEnlarge(); _hiddenLeadingTagsArray.AddEntry(leadingTag, indexInFixMessage, rgId, entryIndex, indexInFixMessage); } internal void AddToHidedTags(int leadingTag, int indexInFixMessage, int rgId, int entryIndex, int virtualTagIndex) { EnsureHidedLeadingTagArrayCapacityAndEnlarge(); _hiddenLeadingTagsArray.AddEntry(leadingTag, indexInFixMessage, rgId, entryIndex, virtualTagIndex); } public bool RemoveRgTagAtIndex(int tagIndex, int parentEntryIndex) { Shift(tagIndex, -1, parentEntryIndex, -1, true); return _storage.RemoveTagAtIndex(tagIndex, false); } public int GetEntryForCreate(int rgIndex, int parentEntryIndex, int index) { //Add entry at some index of repeating group EnsureEntriesCapacityAndEnlarge(); if (_entries[_currentEntry] != null) { _currentEntry++; } _entries[_currentEntry] = RepeatingGroupStorageIntArrayPool.GetIntArrayFromPool(InitialSize); var entry = _entries[_currentEntry]; EnsureRgCapacityAndEnlarge(rgIndex); var repeatingGroup = _rgArray[rgIndex]; _rgArrayManager.AddEntryAtIndex(repeatingGroup, index, _currentEntry); _entriesArray.SetZeroSize(entry); _entriesArray.SetParentEntryLink(entry, parentEntryIndex); return _currentEntry++; } public int GetEntryForCreate(int rgIndex, int parentEntryIndex) { //Add entry at end of repeating group EnsureEntriesCapacityAndEnlarge(); if (_entries[_currentEntry] != null) { _currentEntry++; } _entries[_currentEntry] = RepeatingGroupStorageIntArrayPool.GetIntArrayFromPool(InitialSize); var entry = _entries[_currentEntry]; EnsureRgCapacityAndEnlarge(rgIndex); var repeatingGroup = _rgArray[rgIndex]; _rgArrayManager.AddEntry(repeatingGroup, _currentEntry); _entriesArray.SetZeroSize(entry); _entriesArray.SetParentEntryLink(entry, parentEntryIndex); return _currentEntry++; } private void EnsureHidedLeadingTagArrayCapacityAndEnlarge() { var lastIndex = _hiddenLeadingTagsArray.ArrayEnd; if (lastIndex + HidedEntrySize * 2 >= _hiddenLeadingTags.Length) { var newArr = RepeatingGroupStorageIntArrayPool.GetIntArrayFromPool(_hiddenLeadingTags.Length * 2); Array.Copy(_hiddenLeadingTags, 0, newArr, 0, _hiddenLeadingTags.Length); RepeatingGroupStorageIntArrayPool.ReturnObj(_hiddenLeadingTags); _hiddenLeadingTags = newArr; _hiddenLeadingTagsArray.SetHiddenLeadingTags(_hiddenLeadingTags); } } private void EnsureRepeatingGroupArrayCapacityAndEnlarge() { if (_rgArray[_rgArray.Length - 1] != null) { var newArr = RepeatingGroupStorageIntArrayPool.GetTwoDimIntArrayFromPool(_rgArray.Length * 2); Array.Copy(_rgArray, 0, newArr, 0, _rgArray.Length); RepeatingGroupStorageIntArrayPool.ReturnObj(_rgArray); _rgArray = newArr; _rgArrayManager.SetRgArray(_rgArray); } } internal int[] EnsureEntryCapacityAndEnlarge(int entryIndex) { var entry = _entries[entryIndex]; var currentSize = _entriesArray.GetArrayEnd(entry) + EntriesEntrySize * 2; if (currentSize >= entry.Length - 1) { var newArr = RepeatingGroupStorageIntArrayPool.GetIntArrayFromPool(entry.Length * 2); Array.Copy(entry, 0, newArr, 0, entry.Length); RepeatingGroupStorageIntArrayPool.ReturnObj(entry); _entries[entryIndex] = newArr; } return _entries[entryIndex]; } internal void EnsureEntriesCapacityAndEnlarge() { if (_entries[_entries.Length - 1] != null) { var newArr = RepeatingGroupStorageIntArrayPool.GetTwoDimIntArrayFromPool(_entries.Length * 2); Array.Copy(_entries, 0, newArr, 0, _entries.Length); RepeatingGroupStorageIntArrayPool.ReturnObj(_entries); _entries = newArr; _entriesArray.SetEntries(_entries); } } internal void EnsureRgCapacityAndEnlarge(int rgIndex) { var repeatingGroup = _rgArray[rgIndex]; if (_rgArrayManager.GetRgArrayEnd(repeatingGroup) + RgHashEntrySize * 2 >= repeatingGroup.Length - 1) { var newArr = RepeatingGroupStorageIntArrayPool.GetIntArrayFromPool(repeatingGroup.Length * 2); Array.Copy(repeatingGroup, 0, newArr, 0, repeatingGroup.Length); RepeatingGroupStorageIntArrayPool.ReturnObj(repeatingGroup); _rgArray[rgIndex] = newArr; } } public int[][] Entries => _entries; public bool IsInvalidated { get; private set; } public void AddSubGroup(int index, int leadingTag, int parentEntryLink, RepeatingGroup group, bool validation) { AddSubGroup(index, leadingTag, parentEntryLink, group); @group.Validation = validation; } public RepeatingGroupArray RgArrayManager { get { return _rgArrayManager; } } public EntriesArray EntriesArray { get { return _entriesArray; } } public HiddenLeadingTagsArray HiddenLeadingTagsArray { get { return _hiddenLeadingTagsArray; } } public int GetLeadingTagValue(int leadingTag, int rgId) { var indexInHided = _hiddenLeadingTagsArray.FindInHidedLeadingTags(leadingTag, rgId); if (indexInHided == -1) { var repeatingGroup = _rgArrayManager.GetRgArrayById(leadingTag, rgId); if (repeatingGroup == null || _rgArrayManager.GetRgId(repeatingGroup) == -1) { throw new InvalidOperationException("Can't get leading tag value of nonexistent group"); } var leadingTagIndexInFixMsg = _rgArrayManager.GetRgLeadingTagIndexInFixMsg(repeatingGroup); if (leadingTagIndexInFixMsg == -1) { throw new InvalidOperationException("Can't get leading tag value of removed group"); } return (int)_storage.GetTagValueAsLongAtIndex(leadingTagIndexInFixMsg); } return 0; } public void ShiftIndexes(int startIndex, int offset) { //shift(startIndex, offset, -1, -1, false); if (offset < 0) { for (var rgIndex = 0; rgIndex < _rgArray.Length; rgIndex++) { var rg = _rgArray[rgIndex]; if (rg != null && _rgArrayManager.GetRgId(rg) != -1) { var rgStart = _rgArrayManager.GetRgLeadingTagIndexInFixMsg(rg); var rgEnd = GetRgLastTagIndexInFixMsg(rg); if (rgStart <= startIndex && startIndex <= rgEnd) { //tag is a part of RG return; } } } } else { for (var rgIndex = 0; rgIndex < _rgArray.Length; rgIndex++) { var rg = _rgArray[rgIndex]; if (rg != null && _rgArrayManager.GetRgId(rg) != -1) { var rgStart = _rgArrayManager.GetRgLeadingTagIndexInFixMsg(rg); var rgEnd = GetRgLastTagIndexInFixMsg(rg); Console.WriteLine(_rgArrayManager.GetRgLeadingTag(rg) + " [" + rgStart + ":" + rgEnd + "]"); if (rgStart < startIndex && startIndex <= rgEnd) { //tag is a part of RG return; } } } } Shift(startIndex, offset, -1, -1, false); } private int GetRgLastTagIndexInFixMsg(int[] repeatingGroup) { var lastEntryLink = _rgArrayManager.GetLastEntryLink(repeatingGroup); return _entriesArray.GetLastTagIndexInFixMessage(lastEntryLink); } } }