FixAntenna/NetCore/Message/AbstractFixMessage.cs (331 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;
using System.Collections.Generic;
using Epam.FixAntenna.Constants.Fixt11;
using Epam.FixAntenna.NetCore.Helpers;
using Epam.FixAntenna.NetCore.Message.SpecialTags;
namespace Epam.FixAntenna.NetCore.Message
{
public abstract class AbstractFixMessage : HpExtendedIndexedStorage, IEnumerable<TagValue>
{
//Must be power of two
internal const int InitMaxFields = 32;
protected TagValueIterator Iterator;
internal bool Standalone = true;
public bool IsOriginatingFromPool { get; set; }
/// <summary>
/// Creates an empty message that is engine owned
/// TBD: protect(hide) the constructor from user access. May bee need to have default constructor for user
/// and special - for internal usage
/// TBD: make with default modificator - all other should use NewInstanceFromPool
/// </summary>
/// <seealso cref="IsUserOwned"/>
protected AbstractFixMessage() : this(false)
{
}
/// <summary>
/// Creates an empty message
/// TBD: make with default modificator - all other should use NewInstanceFromPool
/// </summary>
/// <seealso cref="IsUserOwned"> </seealso>
protected AbstractFixMessage(bool isUserOwned) : base(InitMaxFields)
{
Iterator = new TagValueIterator(this);
IsUserOwned = isUserOwned;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public abstract IEnumerator<TagValue> GetEnumerator();
/// <summary>
/// Controls how the message object ownership is handled after the Send.
/// <p/>
/// The message objects that are not owned by user
/// should not be accessed again after FIXSession.sendMessage() call is made
/// <p/>
/// Engine will take a copy of the user owned objects when enqueing them before sending.
/// <p/>
/// The engine owned messages that originate from pool will be freed by
/// the engine automatically after the send. But before sendMessage was called,
/// this is the user's responsibility to return it back using FixMessage.returnInstance()
/// <p/>
/// By default the heap allocated objects (new FixMessage()) are engine owned.
/// <p/>
/// The ownership of objects allocated from pool is explicitely requested in call:
/// FixMessage.NewInstanceFromPool(boolean isUserOwned)
/// </summary>
public bool IsUserOwned { set; get; }
protected internal bool IsPreparedMessage { get; set; }
protected internal bool IsMessageIncomplete { get; set; }
internal bool NeedCloneOnSend => IsUserOwned || ForceCloneOnSend;
internal bool NeedReleaseAfterSend => !IsUserOwned && IsOriginatingFromPool;
internal bool ForceCloneOnSend { get; set; }
protected internal bool IsFree { get; set; } = true;
//TBD! hide from user
internal void SetBuffer(byte[] buf, int offset, int length)
{
Standalone = false;
base.SetOriginalBuffer(buf, offset, length);
}
// usage in NewMessageChopper
//TBD! try to remove external call to this method or hide it from public API
internal sealed override void ShiftBuffer(byte[] buf, int offset, int length)
{
Standalone = false;
base.ShiftBuffer(buf, offset, length);
}
protected internal abstract FixMessage MakeStandalone();
protected internal virtual void SwitchToStandalone()
{
base.TransferDataToArena();
Standalone = true;
}
public int Add(TagValue tagValue)
{
return AddTag(tagValue.TagId, tagValue.Buffer, tagValue.Offset, tagValue.Length);
}
internal int Add(int tag, int offset, int length)
{
return base.MapTagInOrigStorage(tag, offset, length);
}
internal int AddPrepared(int tag, int offset, int length)
{
return base.MapPreparedTagInOrigStorage(tag, offset, length);
}
protected bool AddAll(FixMessage list)
{
var iterator = list.GetTagValueIterator();
while (iterator.MoveNext())
{
var next = iterator.Current;
Add(next);
}
return true;
}
public override void Clear()
{
Standalone = true;
IsMessageIncomplete = false;
IsPreparedMessage = false;
base.Clear(); // TBD! get rid of this
}
public FixMessage DeepCopyTo(FixMessage cloned)
{
cloned.IsPreparedMessage = IsPreparedMessage;
cloned.IsMessageIncomplete = IsMessageIncomplete;
cloned.Standalone = false;
cloned.DeepCopy(this);
cloned.MakeStandalone();
return cloned;
}
protected internal override bool CanCopyInPlace(int index, int oldLen, int newLen)
{
var canCopyInPlace = base.CanCopyInPlace(index, oldLen, newLen);
if (IsPreparedMessage)
{
return canCopyInPlace;
}
return canCopyInPlace && oldLen == newLen;
}
protected internal override bool CanCopyInPlaceNumber(int index, int oldLen, int newLen)
{
var canCopyInPlace = base.CanCopyInPlaceNumber(index, oldLen, newLen);
if (IsPreparedMessage)
{
return canCopyInPlace;
}
return canCopyInPlace && oldLen == newLen;
}
public int GetTagNumAtIndex(int index)
{
return base.GetTagIdAtIndex(index);
}
public int GetTagLength(int tag)
{
return base.GetTagValueLength(tag);
}
// TBD! Add all the UTC timestamp get/set methods for FIX time types
/// <summary>
/// Calculates body length for collection.
/// </summary>
/// <returns> body length </returns>
public int CalculateBodyLength()
{
var length = 0;
var iterator = GetTagValueIterator();
while (iterator.MoveNext())
{
var nextTag = iterator.Current;
var tag = nextTag.TagId;
if (tag > Tags.CheckSum || tag < Tags.BeginString)
{
// do not count tags 8, 9 and 10
length += GetTagBytesLength(tag) + nextTag.Length + 1 + 1; // + '=' + 'SOH'
}
}
return length;
}
/// <summary>
/// Calculates checksum.
/// </summary>
/// <returns> checksum </returns>
//TBD Deprecated
public int CalculateChecksum()
{
long sum = 0;
var iterator = GetTagValueIterator();
while (iterator.MoveNext())
{
var nextTag = iterator.Current;
if (nextTag.TagId != Tags.CheckSum)
{
sum += nextTag.CalculateChecksum() + FieldSeparator;
}
}
return (int)(sum % 256);
}
/// <summary>
/// Converts collection of fix fields to string.
/// </summary>
public sealed override string ToString()
{
return StringHelper.NewString(AsByteArray(DefaultMaskedTags.Instance));
}
/// <summary>
/// Converts collection of fix fields to string, not masking fields 554, 925.
/// </summary>
/// <returns></returns>
public string ToUnmaskedString()
{
return StringHelper.NewString(AsByteArray());
}
/// <summary>
/// Converts collection of FIX fields to string. Some fields (by default 554, 925) masked with asterisks.
/// </summary>
/// <returns>Returns string, where some fields masked with asterisks.</returns>
public string ToPrintableString()
{
return ToPrintableString(DefaultMaskedTags.Instance);
}
/// <summary>
/// Converts collection of FIX fields to string. Some fields (by default 554, 925) masked with asterisks.
/// </summary>
/// <returns>Returns string, where some fields masked with asterisks.</returns>
/// <param name="maskedTags">IMaskedTags instance or null (DefaultMaskedTags will be used).</param>
internal string ToPrintableString(IMaskedTags maskedTags)
{
var tags = maskedTags ?? DefaultMaskedTags.Instance;
var maskedStr = StringHelper.NewString(AsByteArray(tags));
return FixMessagePrintableFormatter.ToPrintableString(maskedStr);
}
/// <summary>
/// Writes field list to array of bytes.
/// </summary>
/// <returns> byte origBuffer </returns>
public byte[] AsByteArray()
{
var result = new byte[RawLength];
ToByteArrayAndReturnNextPosition(result, 0);
return result;
}
/// <summary>
/// Writes field list to array of bytes.
/// </summary>
/// <returns> byte origBuffer </returns>
internal byte[] AsByteArray(IMaskedTags maskedTags)
{
var result = new byte[RawLength];
ToByteArrayAndReturnNextPosition(result, 0, maskedTags);
return result;
}
private int GetTagBytesLength(int tag)
{
var length = 1;
while ((tag /= 10) > 0)
{
length++;
}
return length;
}
public bool IsMessageBufferContinuous
{
get
{
if (!IsPreparedMessage || IsMessageIncomplete)
{
return false;
}
return base.IsAllTagsInOneBuffer;
}
}
/// <summary>
/// Writes the list of field to the <c>origBuffer</c>, and returns the next index.
/// The <c>SOH</c> symbol is added after each field.
/// </summary>
/// <param name="dst"> the origBuffer </param>
/// <param name="offset"> the offset in origBuffer </param>
public int ToByteArrayAndReturnNextPosition(byte[] dst, int offset)
{
return ToByteArrayAndReturnNextPosition(dst, offset, null);
}
internal int ToByteArrayAndReturnNextPosition(byte[] dst, int offset, IMaskedTags maskedTags)
{
if (IsMessageIncomplete)
{
throw new Exception(
"cannot send incomplete message that contains tags skipped by the FIXParseListener user's callback");
}
return IsPreparedMessage
? base.PreparedToByteArrayAndReturnNextPosition(dst, offset, maskedTags)
: base.GenericMessageToByteArrayAndReturnNextPosition(dst, offset, maskedTags);
}
/// <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 <see cref="FixMessage"/>) </returns>
public IList<FixMessage> Split(int tag)
{
var append = false;
IList<FixMessage> result = new List<FixMessage>();
FixMessage list = null;
var iterator = GetTagValueIterator();
while (iterator.MoveNext())
{
var nextTag = iterator.Current;
var currTagId = nextTag.TagId;
if (currTagId == tag)
{
if (list != null)
{
result.Add(list);
}
list = IsOriginatingFromPool
? FixMessageFactory.NewInstanceFromPool(false)
: new FixMessage(false);
append = true;
}
if (append)
{
list.Add(nextTag);
}
}
if (list != null)
{
result.Add(list);
}
return result;
}
protected abstract IList<IDictionary<int, TagValue>> NotifyInvalidMessage(int rgTag, int rgFirstTag);
public sealed override int GetTagIndex(int tag)
{
return base.GetTagIndex(tag);
}
/// <summary>
/// Removes a fix field with specified tag from collection.
/// The methods removes the first occurrence of the specified tag.
/// </summary>
/// <param name="tag"> the fix tag. </param>
/// <returns> <tt>true</tt> if the element was removed. </returns>
public sealed override bool RemoveTag(int tag)
{
var tagIndex = base.GetTagIndex(tag);
return tagIndex != NotFound && RemoveTagAtIndex(tagIndex);
}
public virtual bool IsEmpty
{
get { return Count <= 0; }
}
public override bool Equals(object o)
{
if (this == o)
{
return true;
}
if (o == null || GetType() != o.GetType())
{
return false;
}
var that = (AbstractFixMessage)o;
if (Count != that.Count)
{
return false;
}
var value1 = new TagValue();
var value2 = new TagValue();
try
{
for (var i = 0; i < Count; i++)
{
LoadTagValueByIndex(i, value1);
that.LoadTagValueByIndex(i, value2);
if (!value1.Equals(value2))
{
return false;
}
}
}
catch (FieldNotFoundException)
{
return false;
}
return true;
}
protected internal virtual IEnumerator<TagValue> GetTagValueIterator()
{
Iterator.Reset();
return Iterator;
}
protected sealed class TagValueIterator : IEnumerator<TagValue>
{
private TagValue _instance = new TagValue();
private readonly AbstractFixMessage _message;
private int _iteratorIdx = -1;
public TagValueIterator(AbstractFixMessage message)
{
_message = message;
}
public TagValue Current
{
get
{
_message.LoadTagValueByIndex(_iteratorIdx, _instance);
return _instance;
}
}
object IEnumerator.Current => Current;
public void Reset()
{
_iteratorIdx = -1;
}
public bool MoveNext()
{
if (_iteratorIdx + 1 >= _message.Count)
{
return false;
}
_iteratorIdx++;
return true;
}
public void Dispose()
{
}
}
}
}