Skip to content

Commit aa136ce

Browse files
committed
[Java, C#, C++] Model resetCountToIndex() state machine transition.
Previously, the access order checks did not work with the `resetCountToIndex()` methods (generated on flyweights for repeating groups). Now, the valid transitions using these methods are modelled in the state machine and this is reflected in the code we generate for Java, C# and C++. Note that the `resetCountToIndex()` method does not change the `limit`/`position` of the message; therefore, it is only valid to use it when the `limit` aligns with a boundary between group elements.
1 parent e487b74 commit aa136ce

File tree

7 files changed

+423
-26
lines changed

7 files changed

+423
-26
lines changed

csharp/sbe-tests/FieldAccessOrderCheckTests.cs

+84-9
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,79 @@ public void AllowsEncodingAndDecodingEmptyGroupAndVariableLengthFieldsInSchemaDe
224224
Assert.IsTrue(decoder.ToString().Contains("A=42|B=[]|D='abc'"));
225225
}
226226

227+
[TestMethod]
228+
public void AllowsEncoderToResetZeroGroupLengthToZero()
229+
{
230+
var encoder = new GroupAndVarLength();
231+
encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader);
232+
encoder.A = 42;
233+
encoder.BCount(0).ResetCountToIndex();
234+
encoder.SetD("abc");
235+
encoder.CheckEncodingIsComplete();
236+
237+
var decoder = new GroupAndVarLength();
238+
decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader);
239+
Assert.AreEqual(42, decoder.A);
240+
var bDecoder = decoder.B;
241+
Assert.AreEqual(0, bDecoder.Count);
242+
Assert.AreEqual("abc", decoder.GetD());
243+
Assert.IsTrue(decoder.ToString().Contains("A=42|B=[]|D='abc'"));
244+
}
245+
246+
[TestMethod]
247+
public void AllowsEncoderToResetNonZeroGroupLengthToZeroBeforeCallingNext()
248+
{
249+
var encoder = new GroupAndVarLength();
250+
encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader);
251+
encoder.A = 42;
252+
encoder.BCount(2).ResetCountToIndex();
253+
encoder.SetD("abc");
254+
encoder.CheckEncodingIsComplete();
255+
256+
var decoder = new GroupAndVarLength();
257+
decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader);
258+
Assert.AreEqual(42, decoder.A);
259+
var bDecoder = decoder.B;
260+
Assert.AreEqual(0, bDecoder.Count);
261+
Assert.AreEqual("abc", decoder.GetD());
262+
Assert.IsTrue(decoder.ToString().Contains("A=42|B=[]|D='abc'"));
263+
}
264+
265+
[TestMethod]
266+
public void AllowsEncoderToResetNonZeroGroupLengthToNonZero()
267+
{
268+
var encoder = new GroupAndVarLength();
269+
encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader);
270+
encoder.A = 42;
271+
var bEncoder = encoder.BCount(2);
272+
bEncoder.Next().C = 43;
273+
bEncoder.ResetCountToIndex();
274+
encoder.SetD("abc");
275+
encoder.CheckEncodingIsComplete();
276+
277+
var decoder = new GroupAndVarLength();
278+
decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader);
279+
Assert.AreEqual(42, decoder.A);
280+
var bDecoder = decoder.B;
281+
Assert.AreEqual(1, bDecoder.Count);
282+
Assert.AreEqual(43, bDecoder.Next().C);
283+
Assert.AreEqual("abc", decoder.GetD());
284+
Assert.IsTrue(decoder.ToString().Contains("A=42|B=[(C=43)]|D='abc'"));
285+
}
286+
287+
[TestMethod]
288+
public void DisallowsEncoderToResetGroupLengthMidGroupElement()
289+
{
290+
var encoder = new NestedGroups();
291+
encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader);
292+
encoder.A = 42;
293+
var bEncoder = encoder.BCount(2).Next();
294+
bEncoder.C = 43;
295+
var exception = Assert.ThrowsException<InvalidOperationException>(() => bEncoder.ResetCountToIndex());
296+
Assert.IsTrue(exception.Message.Contains(
297+
"Cannot reset count of repeating group \"b\" in state: V0_B_N_BLOCK"));
298+
}
299+
227300
[TestMethod]
228301
public void DisallowsEncodingGroupElementBeforeCallingNext()
229302
{
@@ -2311,7 +2384,7 @@ public void DisallowsEncodingAsciiInsideGroupBeforeCallingNext5()
23112384
{
23122385
var bEncoder = EncodeUntilGroupWithAsciiInside();
23132386

2314-
Exception exception = Assert.ThrowsException<InvalidOperationException>(() =>
2387+
Exception exception = Assert.ThrowsException<InvalidOperationException>(() =>
23152388
bEncoder.SetC(Encoding.ASCII.GetBytes("EURUSD"), 0));
23162389

23172390
Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N"));
@@ -2322,7 +2395,7 @@ public void DisallowsEncodingAsciiInsideGroupBeforeCallingNext6()
23222395
{
23232396
var bEncoder = EncodeUntilGroupWithAsciiInside();
23242397

2325-
Exception exception = Assert.ThrowsException<InvalidOperationException>(() =>
2398+
Exception exception = Assert.ThrowsException<InvalidOperationException>(() =>
23262399
bEncoder.SetC(Encoding.ASCII.GetBytes("EURUSD")));
23272400

23282401
Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N"));
@@ -2693,7 +2766,7 @@ public void DisallowsEncodingElementOfEmptyGroup2()
26932766
encoder.HCount(0);
26942767

26952768
var ex = Assert.ThrowsException<InvalidOperationException>(() => dEncoder.E = 44);
2696-
Assert.IsTrue(ex.Message.Contains("Cannot access field \"b.d.e\" in state: V0_H_0"));
2769+
Assert.IsTrue(ex.Message.Contains("Cannot access field \"b.d.e\" in state: V0_H_DONE"));
26972770
}
26982771

26992772
[TestMethod]
@@ -2710,7 +2783,7 @@ public void DisallowsEncodingElementOfEmptyGroup3()
27102783
encoder.HCount(0);
27112784

27122785
var ex = Assert.ThrowsException<InvalidOperationException>(() => dEncoder.E = 44);
2713-
Assert.IsTrue(ex.Message.Contains("Cannot access field \"b.d.e\" in state: V0_H_0"));
2786+
Assert.IsTrue(ex.Message.Contains("Cannot access field \"b.d.e\" in state: V0_H_DONE"));
27142787
}
27152788

27162789
[TestMethod]
@@ -2726,7 +2799,7 @@ public void DisallowsEncodingElementOfEmptyGroup4()
27262799
bEncoder.FCount(0);
27272800

27282801
var ex = Assert.ThrowsException<InvalidOperationException>(() => dEncoder.E = 44);
2729-
Assert.IsTrue(ex.Message.Contains("Cannot access field \"b.d.e\" in state: V0_B_1_F_0"));
2802+
Assert.IsTrue(ex.Message.Contains("Cannot access field \"b.d.e\" in state: V0_B_1_F_DONE"));
27302803
}
27312804

27322805

@@ -2738,7 +2811,7 @@ public void DisallowsEncodingElementOfEmptyGroup5()
27382811
encoder.A = 42;
27392812
var bEncoder = encoder.BCount(0);
27402813
var exception = Assert.ThrowsException<InvalidOperationException>(() => bEncoder.C = 43);
2741-
Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V1_B_0"));
2814+
Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V1_B_DONE"));
27422815
}
27432816

27442817
[TestMethod]
@@ -3003,7 +3076,8 @@ public void DisallowsIncompleteMessagesDueToMissingTopLevelGroup1()
30033076
encoder.BCount(0);
30043077
var exception = Assert.ThrowsException<InvalidOperationException>(encoder.CheckEncodingIsComplete);
30053078
StringAssert.Contains(exception.Message,
3006-
"Not fully encoded, current state: V0_B_0, allowed transitions: \"dCount(0)\", \"dCount(>0)\"");
3079+
"Not fully encoded, current state: V0_B_DONE, allowed transitions: " +
3080+
"\"b.resetCountToIndex()\", \"dCount(0)\", \"dCount(>0)\"");
30073081
}
30083082

30093083
[TestMethod]
@@ -3016,7 +3090,8 @@ public void DisallowsIncompleteMessagesDueToMissingTopLevelGroup2()
30163090
bEncoder.C = 2;
30173091
var exception = Assert.ThrowsException<InvalidOperationException>(encoder.CheckEncodingIsComplete);
30183092
StringAssert.Contains(exception.Message,
3019-
"Not fully encoded, current state: V0_B_1_BLOCK, allowed transitions: \"b.c(?)\", \"dCount(0)\", \"dCount(>0)\"");
3093+
"Not fully encoded, current state: V0_B_1_BLOCK, allowed transitions:" +
3094+
" \"b.c(?)\", \"b.resetCountToIndex()\", \"dCount(0)\", \"dCount(>0)\"");
30203095
}
30213096

30223097
[TestMethod]
@@ -3047,7 +3122,7 @@ public void DisallowsIncompleteMessagesDueToMissingNestedGroup1(int bCount, stri
30473122
[DataTestMethod]
30483123
[DataRow(1, 1, "V0_B_1_D_N")]
30493124
[DataRow(1, 2, "V0_B_1_D_N")]
3050-
[DataRow(2, 0, "V0_B_N_D_0")]
3125+
[DataRow(2, 0, "V0_B_N_D_DONE")]
30513126
[DataRow(2, 1, "V0_B_N_D_N")]
30523127
[DataRow(2, 2, "V0_B_N_D_N")]
30533128
public void DisallowsIncompleteMessagesDueToMissingNestedGroup2(

sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/common/AccessOrderModel.java

+66-3
Original file line numberDiff line numberDiff line change
@@ -499,13 +499,13 @@ public void onEnterRepeatingGroup(
499499
final State nRemainingGroup = allocateState(groupPrefix + "N");
500500
final State nRemainingGroupElement = allocateState(groupPrefix + "N_BLOCK");
501501
final State oneRemainingGroupElement = allocateState(groupPrefix + "1_BLOCK");
502-
final State emptyGroup = allocateState(groupPrefix + "0");
502+
final State doneGroup = allocateState(groupPrefix + "DONE");
503503

504504
final Set<State> beginGroupStates = new HashSet<>(currentStates);
505505

506506
// fooCount(0)
507507
final CodecInteraction emptyGroupInteraction = interactionFactory.determineGroupIsEmpty(token);
508-
allocateTransitions(emptyGroupInteraction, beginGroupStates, emptyGroup);
508+
allocateTransitions(emptyGroupInteraction, beginGroupStates, doneGroup);
509509

510510
// fooCount(N) where N > 0
511511
final CodecInteraction nonEmptyGroupInteraction = interactionFactory.determineGroupHasElements(token);
@@ -548,8 +548,16 @@ public void onEnterRepeatingGroup(
548548
);
549549
walkSchemaLevel(oneRemainingCollector, groupFields, groupGroups, groupVarData);
550550

551+
final CodecInteraction resetCountToIndexInteraction = interactionFactory.resetCountToIndex(token);
551552
currentStates.clear();
552-
currentStates.add(emptyGroup);
553+
currentStates.add(doneGroup);
554+
currentStates.add(nRemainingGroup);
555+
currentStates.addAll(nRemainingCollector.exitStates());
556+
currentStates.addAll(oneRemainingCollector.exitStates());
557+
allocateTransitions(resetCountToIndexInteraction, currentStates, doneGroup);
558+
559+
currentStates.clear();
560+
currentStates.add(doneGroup);
553561
currentStates.addAll(oneRemainingCollector.exitStates());
554562
}
555563
}
@@ -935,6 +943,41 @@ String exampleConditions()
935943
}
936944
}
937945

946+
/**
947+
* When the number of elements in a repeating group is set to be its current extent.
948+
*/
949+
private static final class ResetCountToIndex extends CodecInteraction
950+
{
951+
private final String groupPath;
952+
private final Token token;
953+
954+
private ResetCountToIndex(final String groupPath, final Token token)
955+
{
956+
assert groupPath != null;
957+
assert token.signal() == Signal.BEGIN_GROUP;
958+
this.groupPath = groupPath;
959+
this.token = token;
960+
}
961+
962+
@Override
963+
public String groupQualifiedName()
964+
{
965+
return groupPath + token.name();
966+
}
967+
968+
@Override
969+
String exampleCode()
970+
{
971+
return groupPath + token.name() + ".resetCountToIndex()";
972+
}
973+
974+
@Override
975+
String exampleConditions()
976+
{
977+
return "";
978+
}
979+
}
980+
938981
/**
939982
* When the length of a variable length field is accessed without adjusting the position.
940983
*/
@@ -984,6 +1027,7 @@ public static final class HashConsingFactory
9841027
private final Map<Token, CodecInteraction> determineGroupHasElementsInteractions = new HashMap<>();
9851028
private final Map<Token, CodecInteraction> moveToNextElementInteractions = new HashMap<>();
9861029
private final Map<Token, CodecInteraction> moveToLastElementInteractions = new HashMap<>();
1030+
private final Map<Token, CodecInteraction> resetCountToIndexInteractions = new HashMap<>();
9871031
private final Map<Token, CodecInteraction> accessVarDataLengthInteractions = new HashMap<>();
9881032
private final Map<Token, String> groupPathsByField;
9891033
private final Set<Token> topLevelBlockFields;
@@ -1100,6 +1144,25 @@ public CodecInteraction moveToLastElement(final Token token)
11001144
t -> new MoveToLastElement(groupPathsByField.get(t), t));
11011145
}
11021146

1147+
1148+
/**
1149+
* Find or create a {@link CodecInteraction} to represent resetting the count
1150+
* of a repeating group to the current index.
1151+
*
1152+
* <p>For encoders, decoders, and codecs, this will be when the {@code resetCountToIndex}
1153+
* method is called, e.g., {@code myGroup.resetCountToIndex()}.
1154+
*
1155+
* <p>The supplied token must carry a {@link Signal#BEGIN_GROUP} signal.
1156+
*
1157+
* @param token the token identifying the group
1158+
* @return the {@link CodecInteraction} instance
1159+
*/
1160+
public CodecInteraction resetCountToIndex(final Token token)
1161+
{
1162+
return resetCountToIndexInteractions.computeIfAbsent(token,
1163+
t -> new ResetCountToIndex(groupPathsByField.get(t), t));
1164+
}
1165+
11031166
/**
11041167
* Find or create a {@link CodecInteraction} to represent accessing the length
11051168
* of a variable-length data field without advancing the codec position.

sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java

+29
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,33 @@ private static void generateAccessOrderListenerMethodForNextGroupElement(
521521
.append(indent).append("}\n");
522522
}
523523

524+
private static void generateAccessOrderListenerMethodForResetGroupCount(
525+
final StringBuilder sb,
526+
final AccessOrderModel accessOrderModel,
527+
final String indent,
528+
final Token token)
529+
{
530+
if (null == accessOrderModel)
531+
{
532+
return;
533+
}
534+
535+
sb.append(indent).append("void onResetCountToIndex()\n")
536+
.append(indent).append("{\n");
537+
538+
final AccessOrderModel.CodecInteraction resetCountToIndex =
539+
accessOrderModel.interactionFactory().resetCountToIndex(token);
540+
541+
generateAccessOrderListener(
542+
sb,
543+
indent + " ",
544+
"reset count of repeating group",
545+
accessOrderModel,
546+
resetCountToIndex);
547+
548+
sb.append(indent).append("}\n");
549+
}
550+
524551
private void generateGroups(
525552
final StringBuilder sb,
526553
final List<Token> tokens,
@@ -733,6 +760,7 @@ private static void generateGroupClassHeader(
733760
}
734761

735762
generateAccessOrderListenerMethodForNextGroupElement(sb, accessOrderModel, indent + INDENT, groupToken);
763+
generateAccessOrderListenerMethodForResetGroupCount(sb, accessOrderModel, indent, groupToken);
736764

737765
final CharSequence onNextAccessOrderCall = null == accessOrderModel ? "" :
738766
generateAccessOrderListenerCall(accessOrderModel, indent + TWO_INDENT, "onNextElementAccessed");
@@ -803,6 +831,7 @@ private static void generateGroupClassHeader(
803831
sb.append("\n")
804832
.append(indent).append(" inline std::uint64_t resetCountToIndex()\n")
805833
.append(indent).append(" {\n")
834+
.append(generateAccessOrderListenerCall(accessOrderModel, indent + TWO_INDENT, "onResetCountToIndex"))
806835
.append(indent).append(" m_count = m_index;\n")
807836
.append(indent).append(" ").append(dimensionsClassName)
808837
.append(" dimensions(m_buffer, m_initialPosition, m_bufferLength, m_actingVersion);\n")

sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpGenerator.java

+31-1
Original file line numberDiff line numberDiff line change
@@ -344,19 +344,22 @@ private void generateGroupEnumerator(
344344
final String indent)
345345
{
346346
generateAccessOrderListenerMethodForNextGroupElement(sb, accessOrderModel, indent + INDENT, groupToken);
347+
generateAccessOrderListenerMethodForResetGroupCount(sb, accessOrderModel, indent + INDENT, groupToken);
347348

348349
sb.append(
349350
indent + INDENT + "public int ActingBlockLength { get { return _blockLength; } }\n\n" +
350351
indent + INDENT + "public int Count { get { return _count; } }\n\n" +
351-
indent + INDENT + "public bool HasNext { get { return _index < _count; } }\n");
352+
indent + INDENT + "public bool HasNext { get { return _index < _count; } }\n\n");
352353

353354
sb.append(String.format("\n" +
354355
indent + INDENT + "public int ResetCountToIndex()\n" +
355356
indent + INDENT + "{\n" +
357+
"%s" +
356358
indent + INDENT + INDENT + "_count = _index;\n" +
357359
indent + INDENT + INDENT + "_dimensions.NumInGroup = (%s) _count;\n\n" +
358360
indent + INDENT + INDENT + "return _count;\n" +
359361
indent + INDENT + "}\n",
362+
generateAccessOrderListenerCall(accessOrderModel, indent + TWO_INDENT, "OnResetCountToIndex"),
360363
typeForNumInGroup));
361364

362365
sb.append(String.format("\n" +
@@ -1833,6 +1836,33 @@ private static void generateAccessOrderListenerMethodForNextGroupElement(
18331836
.append(indent).append("}\n");
18341837
}
18351838

1839+
private static void generateAccessOrderListenerMethodForResetGroupCount(
1840+
final StringBuilder sb,
1841+
final AccessOrderModel accessOrderModel,
1842+
final String indent,
1843+
final Token token)
1844+
{
1845+
if (null == accessOrderModel)
1846+
{
1847+
return;
1848+
}
1849+
1850+
sb.append(indent).append("private void OnResetCountToIndex()\n")
1851+
.append(indent).append("{\n");
1852+
1853+
final AccessOrderModel.CodecInteraction resetCountToIndex =
1854+
accessOrderModel.interactionFactory().resetCountToIndex(token);
1855+
1856+
generateAccessOrderListener(
1857+
sb,
1858+
indent + " ",
1859+
"reset count of repeating group",
1860+
accessOrderModel,
1861+
resetCountToIndex);
1862+
1863+
sb.append(indent).append("}\n");
1864+
}
1865+
18361866
private static void generateAccessOrderListenerMethodForVarDataLength(
18371867
final StringBuilder sb,
18381868
final AccessOrderModel accessOrderModel,

0 commit comments

Comments
 (0)