From 74498fda136743c49806c4c577e2ba315300430e Mon Sep 17 00:00:00 2001
From: Chris Busbey <cbusbey@connamara.com>
Date: Mon, 23 Nov 2015 16:18:31 -0800
Subject: [PATCH] groups -> field maps

---
 field_map.go            |   7 +-
 repeating_group.go      |  75 ++++++++-----
 repeating_group_test.go | 231 ++++++++++++++++++++++++----------------
 3 files changed, 193 insertions(+), 120 deletions(-)

diff --git a/field_map.go b/field_map.go
index a2024b78..6a253cd4 100644
--- a/field_map.go
+++ b/field_map.go
@@ -103,17 +103,18 @@ func (m FieldMap) GetField(tag Tag, parser FieldValueReader) MessageRejectError
 	return nil
 }
 
-func (m FieldMap) SetField(tag Tag, field FieldValueWriter) {
+func (m FieldMap) SetField(tag Tag, field FieldValueWriter) FieldMap {
 	tValues := make([]TagValue, 1)
 	tValues[0].init(tag, field.Write())
 	m.tagLookup[tag] = tValues
+	return m
 }
 
-func (m FieldMap) Set(field FieldWriter) {
+func (m FieldMap) Set(field FieldWriter) FieldMap {
 	tValues := make([]TagValue, 1)
 	tValues[0].init(field.Tag(), field.Write())
-
 	m.tagLookup[field.Tag()] = tValues
+	return m
 }
 
 func (m FieldMap) sortedTags() []Tag {
diff --git a/repeating_group.go b/repeating_group.go
index 53894c16..d84b2fed 100644
--- a/repeating_group.go
+++ b/repeating_group.go
@@ -2,6 +2,7 @@ package quickfix
 
 import (
 	"bytes"
+	"math"
 	"strconv"
 )
 
@@ -10,30 +11,33 @@ type RepeatingGroupField struct {
 	FieldValue
 }
 
-type Group []RepeatingGroupField
+type GroupTemplate []RepeatingGroupField
+type Group struct{ FieldMap }
 
 type RepeatingGroup struct {
-	GroupTemplate Group
-	Groups        []Group
+	GroupTemplate
+	Groups []Group
 }
 
-func (f *RepeatingGroup) AddGroup(group Group) {
-	f.Groups = append(f.Groups, group)
+func (f *RepeatingGroup) Add() Group {
+	var g Group
+	g.init(f.groupTagOrder())
+
+	f.Groups = append(f.Groups, g)
+	return g
 }
 
 func (f RepeatingGroup) Write() []byte {
 	buf := bytes.NewBufferString(strconv.Itoa(len(f.Groups)))
+	buf.WriteString("")
 
 	for _, group := range f.Groups {
-		for _, field := range group {
-			buf.WriteString("")
-			buf.WriteString(strconv.Itoa(int(field.Tag)))
-			buf.WriteString("=")
-			buf.Write(field.Write())
-		}
+		group.write(buf)
 	}
 
-	return buf.Bytes()
+	//remove the last soh char
+	bytes := buf.Bytes()
+	return bytes[:len(bytes)-1]
 }
 
 func (f RepeatingGroup) findFieldInGroupTemplate(t Tag) (field RepeatingGroupField, ok bool) {
@@ -49,6 +53,28 @@ func (f RepeatingGroup) findFieldInGroupTemplate(t Tag) (field RepeatingGroupFie
 	return
 }
 
+func (f RepeatingGroup) groupTagOrder() tagOrder {
+	tagMap := make(map[Tag]int)
+	for i, f := range f.GroupTemplate {
+		tagMap[f.Tag] = i
+	}
+
+	return func(i, j Tag) bool {
+		orderi := math.MaxInt64
+		orderj := math.MaxInt64
+
+		if iIndex, ok := tagMap[i]; ok {
+			orderi = iIndex
+		}
+
+		if jIndex, ok := tagMap[j]; ok {
+			orderj = jIndex
+		}
+
+		return orderi < orderj
+	}
+}
+
 func (f RepeatingGroup) isDelimiter(t Tag) bool {
 	return t == f.GroupTemplate[0].Tag
 }
@@ -65,7 +91,9 @@ func (f *RepeatingGroup) Read(tv TagValues) (TagValues, error) {
 	}
 
 	tv = tv[1:]
+	tagOrdering := f.groupTagOrder()
 	var group Group
+	group.init(tagOrdering)
 	for len(tv) > 0 {
 		field, ok := f.findFieldInGroupTemplate(tv[0].Tag)
 		if !ok {
@@ -77,17 +105,13 @@ func (f *RepeatingGroup) Read(tv TagValues) (TagValues, error) {
 		}
 
 		if f.isDelimiter(field.Tag) {
-			group = Group{field}
+			group = Group{}
+			group.init(tagOrdering)
+
 			f.Groups = append(f.Groups, group)
-		} else {
-			if len(group) == 0 {
-				// didn't get initial delimiter
-				break
-			}
-
-			group = append(group, field)
-			f.Groups[len(f.Groups)-1] = group
 		}
+
+		group.SetField(field.Tag, field)
 	}
 
 	return tv, err
@@ -95,8 +119,7 @@ func (f *RepeatingGroup) Read(tv TagValues) (TagValues, error) {
 
 func (f RepeatingGroup) Clone() FieldValue {
 	var clone RepeatingGroup
-
-	clone.GroupTemplate = make(Group, len(f.GroupTemplate))
+	clone.GroupTemplate = make(GroupTemplate, len(f.GroupTemplate))
 	clone.Groups = make([]Group, len(f.Groups))
 
 	for i, field := range f.GroupTemplate {
@@ -104,11 +127,7 @@ func (f RepeatingGroup) Clone() FieldValue {
 	}
 
 	for i, group := range f.Groups {
-		clone.Groups[i] = make(Group, len(group))
-
-		for j, field := range group {
-			clone.Groups[i][j] = RepeatingGroupField{field.Tag, field.FieldValue.Clone()}
-		}
+		clone.Groups[i].init(group.tagOrder)
 	}
 
 	return &clone
diff --git a/repeating_group_test.go b/repeating_group_test.go
index 857886ed..8d94ff45 100644
--- a/repeating_group_test.go
+++ b/repeating_group_test.go
@@ -5,28 +5,77 @@ import (
 	"testing"
 )
 
-func TestRepeatingGroup_Write(t *testing.T) {
+func TestRepeatingGroup_Add(t *testing.T) {
+	f := RepeatingGroup{GroupTemplate: GroupTemplate{RepeatingGroupField{1, new(FIXString)}}}
+
 	var tests = []struct {
-		groups   []Group
-		expected []byte
+		expectedLen int
 	}{
-		{[]Group{{RepeatingGroupField{Tag(1), NewFIXString("hello")}}},
-			[]byte("11=hello")},
-		{[]Group{{RepeatingGroupField{Tag(1), NewFIXString("hello")}, RepeatingGroupField{Tag(2), NewFIXString("world")}}},
-			[]byte("11=hello2=world")},
-		{[]Group{{RepeatingGroupField{Tag(1), NewFIXString("hello")}}, {RepeatingGroupField{Tag(1), NewFIXString("world")}}},
-			[]byte("21=hello1=world")},
-		{[]Group{{RepeatingGroupField{Tag(1), NewFIXString("hello")}, RepeatingGroupField{Tag(2), NewFIXString("world")}}, {RepeatingGroupField{Tag(1), NewFIXString("goodbye")}}},
-			[]byte("21=hello2=world1=goodbye")},
+		{1},
+		{2},
 	}
 
 	for _, test := range tests {
-		var f RepeatingGroup
-		for _, group := range test.groups {
-			f.AddGroup(group)
+		g := f.Add()
+		if len(f.Groups) != test.expectedLen {
+			t.Errorf("Expected %v groups, got %v", test.expectedLen, len(f.Groups))
+		}
+
+		g.SetField(Tag(1), FIXString("hello"))
+
+		if !f.Groups[test.expectedLen-1].Has(Tag(1)) {
+			t.Errorf("expected tag in group %v", test.expectedLen)
 		}
 
-		fieldBytes := f.Write()
+		var v FIXString
+		f.Groups[test.expectedLen-1].GetField(Tag(1), &v)
+
+		if string(v) != "hello" {
+			t.Errorf("expected hello in group %v", test.expectedLen)
+		}
+	}
+}
+
+func TestRepeatingGroup_Write(t *testing.T) {
+	f1 := RepeatingGroup{GroupTemplate: GroupTemplate{
+		RepeatingGroupField{1, new(FIXString)},
+		RepeatingGroupField{2, new(FIXString)},
+	}}
+
+	f1.Add().SetField(Tag(1), FIXString("hello"))
+
+	f2 := RepeatingGroup{GroupTemplate: GroupTemplate{
+		RepeatingGroupField{1, new(FIXString)},
+		RepeatingGroupField{2, new(FIXString)},
+	}}
+	f2.Add().SetField(Tag(1), FIXString("hello")).SetField(Tag(2), FIXString("world"))
+
+	f3 := RepeatingGroup{GroupTemplate: GroupTemplate{
+		RepeatingGroupField{1, new(FIXString)},
+		RepeatingGroupField{2, new(FIXString)},
+	}}
+	f3.Add().SetField(Tag(1), FIXString("hello"))
+	f3.Add().SetField(Tag(1), FIXString("world"))
+
+	f4 := RepeatingGroup{GroupTemplate: GroupTemplate{
+		RepeatingGroupField{1, new(FIXString)},
+		RepeatingGroupField{2, new(FIXString)},
+	}}
+	f4.Add().SetField(Tag(1), FIXString("hello")).SetField(Tag(2), FIXString("world"))
+	f4.Add().SetField(Tag(1), FIXString("goodbye"))
+
+	var tests = []struct {
+		f        RepeatingGroup
+		expected []byte
+	}{
+		{f1, []byte("11=hello")},
+		{f2, []byte("11=hello2=world")},
+		{f3, []byte("21=hello1=world")},
+		{f4, []byte("21=hello2=world1=goodbye")},
+	}
+
+	for _, test := range tests {
+		fieldBytes := test.f.Write()
 		if !bytes.Equal(test.expected, fieldBytes) {
 			t.Errorf("expected %s got %s", test.expected, fieldBytes)
 		}
@@ -35,78 +84,71 @@ func TestRepeatingGroup_Write(t *testing.T) {
 
 func TestRepeatingGroup_Read(t *testing.T) {
 
-	singleFieldTemplate := Group{RepeatingGroupField{Tag(1), new(FIXString)}}
-	multiFieldTemplate := Group{RepeatingGroupField{Tag(1), new(FIXString)}, RepeatingGroupField{Tag(2), new(FIXString)}, RepeatingGroupField{Tag(3), new(FIXString)}}
+	singleFieldTemplate := GroupTemplate{RepeatingGroupField{Tag(1), new(FIXString)}}
+	multiFieldTemplate := GroupTemplate{RepeatingGroupField{Tag(1), new(FIXString)}, RepeatingGroupField{Tag(2), new(FIXString)}, RepeatingGroupField{Tag(3), new(FIXString)}}
 
 	tests := []struct {
-		groupTemplate      Group
-		tv                 TagValues
-		expectedGroupNum   int
-		expectedGroupBytes [][][]byte
+		groupTemplate    GroupTemplate
+		tv               TagValues
+		expectedGroupTvs []TagValues
 	}{
-		{singleFieldTemplate, TagValues{TagValue{Value: []byte("0")}}, 0, [][][]byte{}},
-		{singleFieldTemplate, TagValues{TagValue{Value: []byte("1")}, TagValue{Tag: Tag(1), Value: []byte("hello")}}, 1,
-			[][][]byte{
-				[][]byte{
-					[]byte("hello")}}},
-		{singleFieldTemplate, TagValues{TagValue{Value: []byte("1")}, TagValue{Tag: Tag(1), Value: []byte("hello")}, TagValue{Tag: Tag(2), Value: []byte("not in group")}}, 1,
-			[][][]byte{
-				[][]byte{
-					[]byte("hello")}}},
-		{singleFieldTemplate, TagValues{TagValue{Value: []byte("2")}, TagValue{Tag: Tag(1), Value: []byte("hello")}, TagValue{Tag: Tag(1), Value: []byte("world")}}, 2,
-			[][][]byte{
-				[][]byte{[]byte("hello")},
-				[][]byte{[]byte("world")}}},
-		{multiFieldTemplate, TagValues{
-			TagValue{Value: []byte("2")},
-			TagValue{Tag: Tag(1), Value: []byte("hello")},
-			TagValue{Tag: Tag(1), Value: []byte("goodbye")},
-			TagValue{Tag: Tag(2), Value: []byte("cruel")},
-			TagValue{Tag: Tag(3), Value: []byte("world")},
-		}, 2,
-			[][][]byte{
-				[][]byte{[]byte("hello")},
-				[][]byte{[]byte("goodbye"), []byte("cruel"), []byte("world")}}},
-		{multiFieldTemplate, TagValues{
-			TagValue{Value: []byte("3")},
-			TagValue{Tag: Tag(1), Value: []byte("hello")},
-			TagValue{Tag: Tag(1), Value: []byte("goodbye")},
-			TagValue{Tag: Tag(2), Value: []byte("cruel")},
-			TagValue{Tag: Tag(3), Value: []byte("world")},
-			TagValue{Tag: Tag(1), Value: []byte("another")},
-		}, 3,
-			[][][]byte{
-				[][]byte{[]byte("hello")},
-				[][]byte{[]byte("goodbye"), []byte("cruel"), []byte("world")},
-				[][]byte{[]byte("another")}}},
+		{singleFieldTemplate, TagValues{TagValue{Value: []byte("0")}},
+			[]TagValues{}},
+		{singleFieldTemplate, TagValues{TagValue{Value: []byte("1")}, TagValue{Tag: Tag(1), Value: []byte("hello")}},
+			[]TagValues{TagValues{TagValue{Tag: Tag(1), Value: []byte("hello")}}}},
+		{singleFieldTemplate,
+			TagValues{TagValue{Value: []byte("1")},
+				TagValue{Tag: Tag(1), Value: []byte("hello")},
+				TagValue{Tag: Tag(2), Value: []byte("not in group")}},
+			[]TagValues{
+				TagValues{TagValue{Tag: Tag(1), Value: []byte("hello")}}}},
+		{singleFieldTemplate,
+			TagValues{TagValue{Value: []byte("2")},
+				TagValue{Tag: Tag(1), Value: []byte("hello")},
+				TagValue{Tag: Tag(1), Value: []byte("world")}},
+			[]TagValues{
+				TagValues{TagValue{Tag: Tag(1), Value: []byte("hello")}},
+				TagValues{TagValue{Tag: Tag(1), Value: []byte("world")}},
+			}},
+		{multiFieldTemplate,
+			TagValues{
+				TagValue{Value: []byte("2")},
+				TagValue{Tag: Tag(1), Value: []byte("hello")},
+				TagValue{Tag: Tag(1), Value: []byte("goodbye")}, TagValue{Tag: Tag(2), Value: []byte("cruel")}, TagValue{Tag: Tag(3), Value: []byte("world")},
+			},
+			[]TagValues{
+				TagValues{TagValue{Tag: Tag(1), Value: []byte("hello")}},
+				TagValues{TagValue{Tag: Tag(1), Value: []byte("goodbye")}, TagValue{Tag: Tag(2), Value: []byte("cruel")}, TagValue{Tag: Tag(3), Value: []byte("world")}},
+			}},
+		{multiFieldTemplate,
+			TagValues{
+				TagValue{Value: []byte("3")},
+				TagValue{Tag: Tag(1), Value: []byte("hello")},
+				TagValue{Tag: Tag(1), Value: []byte("goodbye")}, TagValue{Tag: Tag(2), Value: []byte("cruel")}, TagValue{Tag: Tag(3), Value: []byte("world")},
+				TagValue{Tag: Tag(1), Value: []byte("another")},
+			},
+			[]TagValues{
+				TagValues{TagValue{Tag: Tag(1), Value: []byte("hello")}},
+				TagValues{TagValue{Tag: Tag(1), Value: []byte("goodbye")}, TagValue{Tag: Tag(2), Value: []byte("cruel")}, TagValue{Tag: Tag(3), Value: []byte("world")}},
+				TagValues{TagValue{Tag: Tag(1), Value: []byte("another")}},
+			}},
 	}
 
 	for _, test := range tests {
 		f := RepeatingGroup{GroupTemplate: test.groupTemplate}
 
 		f.Read(test.tv)
-		if len(f.Groups) != test.expectedGroupNum {
-			t.Errorf("expected %v groups, got %v", test.expectedGroupNum, len(f.Groups))
-		}
-
-		var allGroupBytes [][][]byte
-		for _, group := range f.Groups {
-			var groupBytes [][]byte
-			for _, field := range group {
-				groupBytes = append(groupBytes, field.Write())
-			}
-
-			allGroupBytes = append(allGroupBytes, groupBytes)
+		if len(f.Groups) != len(test.expectedGroupTvs) {
+			t.Errorf("expected %v groups, got %v", len(test.expectedGroupTvs), len(f.Groups))
 		}
 
-		for i, groupBytes := range allGroupBytes {
-			if len(groupBytes) != len(test.expectedGroupBytes[i]) {
-				t.Errorf("Expected %v fields for group %v (%s) got %v (%s)", len(test.expectedGroupBytes[i]), i, test.expectedGroupBytes[i], len(groupBytes), groupBytes)
-			}
+		for g, group := range f.Groups {
+			for _, expected := range test.expectedGroupTvs[g] {
+				var actual FIXString
+				group.GetField(expected.Tag, &actual)
 
-			for j, fieldBytes := range groupBytes {
-				if !bytes.Equal(fieldBytes, test.expectedGroupBytes[i][j]) {
-					t.Errorf("Expected '%s' for field %v of group %v, got '%s'", test.expectedGroupBytes[i][j], j, i, fieldBytes)
+				if !bytes.Equal(expected.Value, []byte(actual)) {
+					t.Errorf("%v, %v: expected %s, got %s", g, expected.Tag, expected.Value, actual)
 				}
 			}
 		}
@@ -123,7 +165,14 @@ func TestRepeatingGroup_ReadComplete(t *testing.T) {
 		t.Error("Unexpected error, ", err)
 	}
 
-	template := Group{RepeatingGroupField{Tag(269), new(FIXString)}, RepeatingGroupField{Tag(270), new(FIXString)}, RepeatingGroupField{Tag(271), new(FIXString)}, RepeatingGroupField{Tag(272), new(FIXString)}, RepeatingGroupField{Tag(273), new(FIXString)}}
+	template := GroupTemplate{
+		RepeatingGroupField{Tag(269), new(FIXString)},
+		RepeatingGroupField{Tag(270), new(FIXString)},
+		RepeatingGroupField{Tag(271), new(FIXString)},
+		RepeatingGroupField{Tag(272), new(FIXString)},
+		RepeatingGroupField{Tag(273), new(FIXString)},
+	}
+
 	f := RepeatingGroup{GroupTemplate: template}
 	err = msg.Body.GetField(Tag(268), &f)
 	if err != nil {
@@ -140,27 +189,31 @@ func TestRepeatingGroup_ReadComplete(t *testing.T) {
 		[]Tag{Tag(269), Tag(270), Tag(272), Tag(273)},
 		[]Tag{Tag(269), Tag(271), Tag(272), Tag(273)},
 	}
-	expectedGroupBytes := [][][]byte{
-		[][]byte{[]byte("4"), []byte("0.07499"), []byte("20151027"), []byte("18:41:52.698")},
-		[][]byte{[]byte("7"), []byte("0.07501"), []byte("20151027"), []byte("18:41:52.698")},
-		[][]byte{[]byte("8"), []byte("0.07494"), []byte("20151027"), []byte("18:41:52.698")},
-		[][]byte{[]byte("B"), []byte("60"), []byte("20151027"), []byte("18:41:52.698")},
+
+	expectedGroupValues := [][]FIXString{
+		[]FIXString{FIXString("4"), FIXString("0.07499"), FIXString("20151027"), FIXString("18:41:52.698")},
+		[]FIXString{FIXString("7"), FIXString("0.07501"), FIXString("20151027"), FIXString("18:41:52.698")},
+		[]FIXString{FIXString("8"), FIXString("0.07494"), FIXString("20151027"), FIXString("18:41:52.698")},
+		[]FIXString{FIXString("B"), FIXString("60"), FIXString("20151027"), FIXString("18:41:52.698")},
 	}
 
 	for i, group := range f.Groups {
-		if len(group) != len(expectedGroupTags[i]) {
-			t.Errorf("expected %v tags in group %v got %v", len(expectedGroupTags[i]), i, len(group))
-		}
 
-		for j, field := range group {
-			if field.Tag != expectedGroupTags[i][j] {
-				t.Errorf("expected %v in group %v, field %v got %v", expectedGroupTags[i][j], i, j, field.Tag)
+		for j, tag := range expectedGroupTags[i] {
+			if !group.Has(tag) {
+				t.Errorf("expected %v in group %v", expectedGroupTags[i][j], i)
+				continue
 			}
 
-			if !bytes.Equal(field.Write(), expectedGroupBytes[i][j]) {
-				t.Errorf("Expected '%s' for field %v of group %v, got '%s'", expectedGroupBytes[i][j], j, i, field.Write())
+			var actual FIXString
+			if err := group.GetField(tag, &actual); err != nil {
+				t.Errorf("error getting field %v from group %v", tag, i)
+				continue
 			}
 
+			if expectedGroupValues[i][j] != actual {
+				t.Errorf("Expected %v got %v", expectedGroupTags[i][j], actual)
+			}
 		}
 	}
 }
-- 
GitLab