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

230 lines
5.4 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 (
"fmt"
"math"
"strconv"
)
// GroupItem interface is used to construct repeating group templates.
type GroupItem interface {
// Tag returns the tag identifying this GroupItem.
Tag() Tag
// Read Parameter to Read is tagValues. For most fields, only the first tagValue will be required.
// The length of the slice extends from the tagValue mapped to the field to be read through the
// following fields. This can be useful for GroupItems made up of repeating groups.
//
// The Read function returns the remaining tagValues not processed by the GroupItem. If there was a
// problem reading the field, an error may be returned.
Read([]TagValue) ([]TagValue, error)
// Clone makes a copy of this GroupItem.
Clone() GroupItem
}
type protoGroupElement struct {
tag Tag
}
func (t protoGroupElement) Tag() Tag { return t.tag }
func (t protoGroupElement) Read(tv []TagValue) ([]TagValue, error) {
if tv[0].tag == t.tag {
return tv[1:], nil
}
return tv, nil
}
func (t protoGroupElement) Clone() GroupItem { return t }
// GroupElement returns a GroupItem made up of a single field.
func GroupElement(tag Tag) GroupItem {
return protoGroupElement{tag: tag}
}
// GroupTemplate specifies the group item order for a RepeatingGroup.
type GroupTemplate []GroupItem
// Clone makes a copy of this GroupTemplate.
func (gt GroupTemplate) Clone() GroupTemplate {
clone := make(GroupTemplate, len(gt))
for i := range gt {
clone[i] = gt[i].Clone()
}
return clone
}
// Group is a group of fields occurring in a repeating group.
type Group struct{ FieldMap }
// RepeatingGroup is a FIX Repeating Group type.
type RepeatingGroup struct {
tag Tag
template GroupTemplate
groups []*Group
}
// NewRepeatingGroup returns an initilized RepeatingGroup instance.
func NewRepeatingGroup(tag Tag, template GroupTemplate) *RepeatingGroup {
return &RepeatingGroup{
tag: tag,
template: template,
}
}
// Tag returns the Tag for this repeating Group.
func (f RepeatingGroup) Tag() Tag {
return f.tag
}
// Clone makes a copy of this RepeatingGroup (tag, template).
func (f RepeatingGroup) Clone() GroupItem {
return &RepeatingGroup{
tag: f.tag,
template: f.template.Clone(),
}
}
// Len returns the number of Groups in this RepeatingGroup.
func (f RepeatingGroup) Len() int {
return len(f.groups)
}
// Get returns the ith group in this RepeatingGroup.
func (f RepeatingGroup) Get(i int) *Group {
return f.groups[i]
}
// Add appends a new group to the RepeatingGroup and returns the new Group.
func (f *RepeatingGroup) Add() *Group {
g := new(Group)
g.initWithOrdering(f.groupTagOrder())
f.groups = append(f.groups, g)
return g
}
// Write returns tagValues for all Items in the repeating group ordered by
// Group sequence and Group template order.
func (f RepeatingGroup) Write() []TagValue {
tvs := make([]TagValue, 1)
tvs[0].init(f.tag, []byte(strconv.Itoa(len(f.groups))))
for _, group := range f.groups {
tags := group.sortedTags()
group.rwLock.RLock()
for _, tag := range tags {
if fields, ok := group.tagLookup[tag]; ok {
tvs = append(tvs, fields...)
}
}
group.rwLock.RUnlock()
}
return tvs
}
func (f RepeatingGroup) findItemInGroupTemplate(t Tag) (item GroupItem, ok bool) {
for _, templateField := range f.template {
if t == templateField.Tag() {
ok = true
item = templateField.Clone()
break
}
}
return
}
func (f RepeatingGroup) groupTagOrder() tagOrder {
tagMap := make(map[Tag]int)
for i, f := range f.template {
tagMap[f.Tag()] = i
}
return func(i, j Tag) bool {
orderi := math.MaxInt32
orderj := math.MaxInt32
if iIndex, ok := tagMap[i]; ok {
orderi = iIndex
}
if jIndex, ok := tagMap[j]; ok {
orderj = jIndex
}
return orderi < orderj
}
}
func (f RepeatingGroup) delimiter() Tag {
return f.template[0].Tag()
}
func (f RepeatingGroup) isDelimiter(t Tag) bool {
return t == f.delimiter()
}
func (f *RepeatingGroup) Read(tv []TagValue) ([]TagValue, error) {
expectedGroupSize, err := atoi(tv[0].value)
if err != nil {
return tv, err
}
if expectedGroupSize == 0 {
return tv[1:], nil
}
tv = tv[1:cap(tv)]
tagOrdering := f.groupTagOrder()
group := new(Group)
group.initWithOrdering(tagOrdering)
for len(tv) > 0 {
gi, ok := f.findItemInGroupTemplate(tv[0].tag)
if !ok {
break
}
tvRange := tv
if tv, err = gi.Read(tv); err != nil {
return tv, err
}
if f.isDelimiter(gi.Tag()) {
group = new(Group)
group.initWithOrdering(tagOrdering)
f.groups = append(f.groups, group)
}
group.rwLock.Lock()
group.tagLookup[tvRange[0].tag] = tvRange
group.tags = append(group.tags, gi.Tag())
group.rwLock.Unlock()
}
if len(f.groups) != expectedGroupSize {
return tv, repeatingGroupFieldsOutOfOrder(f.tag, fmt.Sprintf("group %v: template is wrong or delimiter %v not found: expected %v groups, but found %v", f.tag, f.delimiter(), expectedGroupSize, len(f.groups)))
}
return tv, err
}