Files
qfixdpl/quickfix/field_map.go
2026-03-09 15:35:32 -03:00

415 lines
9.2 KiB
Go

// Copyright (c) quickfixengine.org All rights reserved.
//
// This file may be distributed under the terms of the quickfixengine.org
// license as defined by quickfixengine.org and appearing in the file
// LICENSE included in the packaging of this file.
//
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE.
//
// See http://www.quickfixengine.org/LICENSE for licensing information.
//
// Contact ask@quickfixengine.org if any conditions of this licensing
// are not clear to you.
package quickfix
import (
"bytes"
"sort"
"sync"
"time"
)
// field stores a slice of TagValues.
type field []TagValue
func fieldTag(f field) Tag {
return f[0].tag
}
func initField(f field, tag Tag, value []byte) {
f[0].init(tag, value)
}
func writeField(f field, buffer *bytes.Buffer) {
for _, tv := range f {
buffer.Write(tv.bytes)
}
}
// tagOrder true if tag i should occur before tag j.
type tagOrder func(i, j Tag) bool
type tagSort struct {
tags []Tag
compare tagOrder
}
func (t tagSort) Len() int { return len(t.tags) }
func (t tagSort) Swap(i, j int) { t.tags[i], t.tags[j] = t.tags[j], t.tags[i] }
func (t tagSort) Less(i, j int) bool { return t.compare(t.tags[i], t.tags[j]) }
// FieldMap is a collection of fix fields that make up a fix message.
type FieldMap struct {
tagLookup map[Tag]field
tagSort
rwLock *sync.RWMutex
}
// ascending tags.
func normalFieldOrder(i, j Tag) bool { return i < j }
func (m *FieldMap) init() {
m.initWithOrdering(normalFieldOrder)
}
func (m *FieldMap) initWithOrdering(ordering tagOrder) {
m.rwLock = &sync.RWMutex{}
m.tagLookup = make(map[Tag]field)
m.compare = ordering
}
// Tags returns all of the Field Tags in this FieldMap.
func (m FieldMap) Tags() []Tag {
m.rwLock.RLock()
defer m.rwLock.RUnlock()
tags := make([]Tag, 0, len(m.tagLookup))
for t := range m.tagLookup {
tags = append(tags, t)
}
return tags
}
// Get parses out a field in this FieldMap. Returned reject may indicate the field is not present, or the field value is invalid.
func (m FieldMap) Get(parser Field) MessageRejectError {
return m.GetField(parser.Tag(), parser)
}
// Has returns true if the Tag is present in this FieldMap.
func (m FieldMap) Has(tag Tag) bool {
m.rwLock.RLock()
defer m.rwLock.RUnlock()
_, ok := m.tagLookup[tag]
return ok
}
// GetField parses of a field with Tag tag. Returned reject may indicate the field is not present, or the field value is invalid.
func (m FieldMap) GetField(tag Tag, parser FieldValueReader) MessageRejectError {
m.rwLock.RLock()
defer m.rwLock.RUnlock()
f, ok := m.tagLookup[tag]
if !ok {
return ConditionallyRequiredFieldMissing(tag)
}
if err := parser.Read(f[0].value); err != nil {
return IncorrectDataFormatForValue(tag)
}
return nil
}
// GetField parses of a field with Tag tag. Returned reject may indicate the field is not present, or the field value is invalid.
func (m FieldMap) getFieldNoLock(tag Tag, parser FieldValueReader) MessageRejectError {
f, ok := m.tagLookup[tag]
if !ok {
return ConditionallyRequiredFieldMissing(tag)
}
if err := parser.Read(f[0].value); err != nil {
return IncorrectDataFormatForValue(tag)
}
return nil
}
// GetBytes is a zero-copy GetField wrapper for []bytes fields.
func (m FieldMap) GetBytes(tag Tag) ([]byte, MessageRejectError) {
m.rwLock.RLock()
defer m.rwLock.RUnlock()
f, ok := m.tagLookup[tag]
if !ok {
return nil, ConditionallyRequiredFieldMissing(tag)
}
return f[0].value, nil
}
// getBytesNoLock is a lock free zero-copy GetField wrapper for []bytes fields.
func (m FieldMap) getBytesNoLock(tag Tag) ([]byte, MessageRejectError) {
f, ok := m.tagLookup[tag]
if !ok {
return nil, ConditionallyRequiredFieldMissing(tag)
}
return f[0].value, nil
}
// GetBool is a GetField wrapper for bool fields.
func (m FieldMap) GetBool(tag Tag) (bool, MessageRejectError) {
var val FIXBoolean
if err := m.GetField(tag, &val); err != nil {
return false, err
}
return bool(val), nil
}
// GetInt is a GetField wrapper for int fields.
func (m FieldMap) GetInt(tag Tag) (int, MessageRejectError) {
bytes, err := m.GetBytes(tag)
if err != nil {
return 0, err
}
var val FIXInt
if val.Read(bytes) != nil {
err = IncorrectDataFormatForValue(tag)
}
return int(val), err
}
// GetInt is a lock free GetField wrapper for int fields.
func (m FieldMap) getIntNoLock(tag Tag) (int, MessageRejectError) {
bytes, err := m.getBytesNoLock(tag)
if err != nil {
return 0, err
}
var val FIXInt
if val.Read(bytes) != nil {
err = IncorrectDataFormatForValue(tag)
}
return int(val), err
}
// GetTime is a GetField wrapper for utc timestamp fields.
func (m FieldMap) GetTime(tag Tag) (t time.Time, err MessageRejectError) {
m.rwLock.RLock()
defer m.rwLock.RUnlock()
bytes, err := m.GetBytes(tag)
if err != nil {
return
}
var val FIXUTCTimestamp
if val.Read(bytes) != nil {
err = IncorrectDataFormatForValue(tag)
}
return val.Time, err
}
// GetString is a GetField wrapper for string fields.
func (m FieldMap) GetString(tag Tag) (string, MessageRejectError) {
var val FIXString
if err := m.GetField(tag, &val); err != nil {
return "", err
}
return string(val), nil
}
// GetString is a GetField wrapper for string fields.
func (m FieldMap) getStringNoLock(tag Tag) (string, MessageRejectError) {
var val FIXString
if err := m.getFieldNoLock(tag, &val); err != nil {
return "", err
}
return string(val), nil
}
// GetGroup is a Get function specific to Group Fields.
func (m FieldMap) GetGroup(parser FieldGroupReader) MessageRejectError {
m.rwLock.RLock()
defer m.rwLock.RUnlock()
f, ok := m.tagLookup[parser.Tag()]
if !ok {
return ConditionallyRequiredFieldMissing(parser.Tag())
}
if _, err := parser.Read(f); err != nil {
if msgRejErr, ok := err.(MessageRejectError); ok {
return msgRejErr
}
return IncorrectDataFormatForValue(parser.Tag())
}
return nil
}
// SetField sets the field with Tag tag.
func (m *FieldMap) SetField(tag Tag, field FieldValueWriter) *FieldMap {
return m.SetBytes(tag, field.Write())
}
// SetBytes sets bytes.
func (m *FieldMap) SetBytes(tag Tag, value []byte) *FieldMap {
f := m.getOrCreate(tag)
initField(f, tag, value)
return m
}
// SetBool is a SetField wrapper for bool fields.
func (m *FieldMap) SetBool(tag Tag, value bool) *FieldMap {
return m.SetField(tag, FIXBoolean(value))
}
// SetInt is a SetField wrapper for int fields.
func (m *FieldMap) SetInt(tag Tag, value int) *FieldMap {
v := FIXInt(value)
return m.SetBytes(tag, v.Write())
}
// SetString is a SetField wrapper for string fields.
func (m *FieldMap) SetString(tag Tag, value string) *FieldMap {
return m.SetBytes(tag, []byte(value))
}
// Remove removes a tag from field map.
func (m *FieldMap) Remove(tag Tag) {
m.rwLock.Lock()
defer m.rwLock.Unlock()
delete(m.tagLookup, tag)
}
// Clear purges all fields from field map.
func (m *FieldMap) Clear() {
m.rwLock.Lock()
defer m.rwLock.Unlock()
m.tags = m.tags[0:0]
for k := range m.tagLookup {
delete(m.tagLookup, k)
}
}
func (m *FieldMap) clearNoLock() {
m.tags = m.tags[0:0]
for k := range m.tagLookup {
delete(m.tagLookup, k)
}
}
// CopyInto overwrites the given FieldMap with this one.
func (m *FieldMap) CopyInto(to *FieldMap) {
m.rwLock.RLock()
defer m.rwLock.RUnlock()
to.tagLookup = make(map[Tag]field)
for tag, f := range m.tagLookup {
clone := make(field, 1)
clone[0] = f[0]
to.tagLookup[tag] = clone
}
to.tags = make([]Tag, len(m.tags))
copy(to.tags, m.tags)
to.compare = m.compare
}
func (m *FieldMap) add(f field) {
t := fieldTag(f)
if _, ok := m.tagLookup[t]; !ok {
m.tags = append(m.tags, t)
}
m.tagLookup[t] = f
}
func (m *FieldMap) getOrCreate(tag Tag) field {
m.rwLock.Lock()
defer m.rwLock.Unlock()
if f, ok := m.tagLookup[tag]; ok {
f = f[:1]
return f
}
f := make(field, 1)
m.tagLookup[tag] = f
m.tags = append(m.tags, tag)
return f
}
// Set is a setter for fields.
func (m *FieldMap) Set(field FieldWriter) *FieldMap {
f := m.getOrCreate(field.Tag())
initField(f, field.Tag(), field.Write())
return m
}
// SetGroup is a setter specific to group fields.
func (m *FieldMap) SetGroup(field FieldGroupWriter) *FieldMap {
m.rwLock.Lock()
defer m.rwLock.Unlock()
_, ok := m.tagLookup[field.Tag()]
if !ok {
m.tags = append(m.tags, field.Tag())
}
m.tagLookup[field.Tag()] = field.Write()
return m
}
func (m *FieldMap) sortedTags() []Tag {
sort.Sort(m)
return m.tags
}
func (m FieldMap) write(buffer *bytes.Buffer) {
m.rwLock.Lock()
defer m.rwLock.Unlock()
for _, tag := range m.sortedTags() {
if f, ok := m.tagLookup[tag]; ok {
writeField(f, buffer)
}
}
}
func (m FieldMap) total() int {
m.rwLock.RLock()
defer m.rwLock.RUnlock()
total := 0
for _, fields := range m.tagLookup {
for _, tv := range fields {
switch tv.tag {
case tagCheckSum: // Tag does not contribute to total.
default:
total += tv.total()
}
}
}
return total
}
func (m FieldMap) length() int {
m.rwLock.RLock()
defer m.rwLock.RUnlock()
length := 0
for _, fields := range m.tagLookup {
for _, tv := range fields {
switch tv.tag {
case tagBeginString, tagBodyLength, tagCheckSum: // Tags do not contribute to length.
default:
length += tv.length()
}
}
}
return length
}