diff --git a/.gitignore b/.gitignore index 117022a886..6769c8db69 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,9 @@ out # cmake cmake-build-debug codecs +.cmake +CMakeFiles +thirdparty # cpp build linux cppbuild/CMakeCache.txt @@ -101,10 +104,12 @@ csharp/sbe-generated/issue483 csharp/sbe-generated/issue560 csharp/sbe-generated/issue661 csharp/sbe-generated/since-deprecated +csharp/sbe-generated/order_check csharp/sbe-generated/mktdata/*.cs csharp/sbe-generated/uk_co_real_logic_sbe_benchmarks_fix csharp/sbe-tests/*.sbe csharp/nuget/ +csharp/csharp.sln.DotSettings.user # rust rust/target diff --git a/build.gradle b/build.gradle index 2761b537e4..2e9843aa4f 100644 --- a/build.gradle +++ b/build.gradle @@ -229,6 +229,9 @@ subprojects { } javaLauncher.set(toolchainLauncher) + + systemProperty 'sbe.enable.ir.precedence.checks', 'true' + systemProperty 'sbe.enable.test.precedence.checks', 'true' } } @@ -279,9 +282,12 @@ project(':sbe-tool') { 'sbe.output.dir': generatedDir, 'sbe.target.language': 'Java', 'sbe.validation.stop.on.error': 'true', - 'sbe.validation.xsd': validationXsdPath) + 'sbe.validation.xsd': validationXsdPath, + 'sbe.generate.precedence.checks': 'true', + 'sbe.java.precedence.checks.property.name': 'sbe.enable.test.precedence.checks') args = ['src/test/resources/json-printer-test-schema.xml', - 'src/test/resources/composite-elements-schema.xml'] + 'src/test/resources/composite-elements-schema.xml', + 'src/test/resources/field-order-check-schema.xml'] } jar { @@ -541,7 +547,8 @@ project(':sbe-benchmarks') { 'sbe.validation.stop.on.error': 'true', 'sbe.validation.xsd': validationXsdPath, 'sbe.java.encoding.buffer.type': 'org.agrona.concurrent.UnsafeBuffer', - 'sbe.java.decoding.buffer.type': 'org.agrona.concurrent.UnsafeBuffer') + 'sbe.java.decoding.buffer.type': 'org.agrona.concurrent.UnsafeBuffer', + 'sbe.generate.precedence.checks': 'false') args = ['src/main/resources/car.xml', 'src/main/resources/fix-message-samples.xml'] } @@ -730,13 +737,15 @@ tasks.register('generateCSharpCodecsTests', JavaExec) { 'sbe.output.dir': 'csharp/sbe-generated', 'sbe.target.language': 'uk.co.real_logic.sbe.generation.csharp.CSharp', 'sbe.xinclude.aware': 'true', - 'sbe.validation.xsd': validationXsdPath) + 'sbe.validation.xsd': validationXsdPath, + 'sbe.generate.precedence.checks': 'true') args = ['sbe-tool/src/test/resources/FixBinary.xml', 'sbe-tool/src/test/resources/issue435.xml', 'sbe-tool/src/test/resources/issue483.xml', 'sbe-tool/src/test/resources/issue560.xml', 'sbe-tool/src/test/resources/since-deprecated-test-schema.xml', 'sbe-tool/src/test/resources/example-bigendian-test-schema.xml', + 'sbe-tool/src/test/resources/field-order-check-schema.xml', 'sbe-benchmarks/src/main/resources/fix-message-samples.xml'] } @@ -753,7 +762,10 @@ tasks.register('generateJavaIrCodecs', JavaExec) { systemProperties( 'sbe.output.dir': 'sbe-tool/src/main/java', 'sbe.target.language': 'Java', - 'sbe.validation.xsd': validationXsdPath) + 'sbe.validation.xsd': validationXsdPath, + 'sbe.generate.precedence.checks': 'true', + 'sbe.precedence.checks.flag.name': 'SBE_ENABLE_IR_PRECEDENCE_CHECKS', + 'sbe.java.precedence.checks.property.name': 'sbe.enable.ir.precedence.checks') args = ['sbe-tool/src/main/resources/sbe-ir.xml'] } @@ -763,7 +775,9 @@ tasks.register('generateCppIrCodecs', JavaExec) { systemProperties( 'sbe.output.dir': 'sbe-tool/src/main/cpp', 'sbe.target.language': 'cpp', - 'sbe.validation.xsd': validationXsdPath) + 'sbe.validation.xsd': validationXsdPath, + 'sbe.generate.precedence.checks': 'true', + 'sbe.precedence.checks.flag.name': 'SBE_ENABLE_IR_PRECEDENCE_CHECKS') args = ['sbe-tool/src/main/resources/sbe-ir.xml'] } diff --git a/csharp/sbe-generated/sbe-generated.csproj b/csharp/sbe-generated/sbe-generated.csproj index 6b4fc7bdbc..806168d7d5 100644 --- a/csharp/sbe-generated/sbe-generated.csproj +++ b/csharp/sbe-generated/sbe-generated.csproj @@ -7,6 +7,14 @@ Copyright (C) Bill Segall 2018, MarketFactory Inc 2017, Adaptive 2014. All rights reserved. + + TRACE,SBE_ENABLE_PRECEDENCE_CHECKS + + + + TRACE,SBE_ENABLE_PRECEDENCE_CHECKS + + diff --git a/csharp/sbe-tests/FieldAccessOrderCheckTests.cs b/csharp/sbe-tests/FieldAccessOrderCheckTests.cs new file mode 100644 index 0000000000..d0b7e16992 --- /dev/null +++ b/csharp/sbe-tests/FieldAccessOrderCheckTests.cs @@ -0,0 +1,3185 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Order.Check; +using Org.SbeTool.Sbe.Dll; + +namespace Org.SbeTool.Sbe.Tests +{ + [TestClass] + public class FieldAccessOrderCheckTests + { + private const int Offset = 0; + private readonly DirectBuffer _buffer = new DirectBuffer(); + private readonly MessageHeader _messageHeader = new MessageHeader(); + + [TestInitialize] + public void SetUp() + { + var byteArray = new byte[128]; + new Random().NextBytes(byteArray); + _buffer.Wrap(byteArray); + } + + [TestMethod] + public void AllowsEncodingAndDecodingVariableLengthFieldsInSchemaDefinedOrder() + { + var encoder = new MultipleVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.SetB("abc"); + encoder.SetC("def"); + encoder.CheckEncodingIsComplete(); + + var decoder = new MultipleVarLength(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + Assert.AreEqual("abc", decoder.GetB()); + Assert.AreEqual("def", decoder.GetC()); + Assert.IsTrue(decoder.ToString().Contains("A=42|B='abc'|C='def'")); + } + + [TestMethod] + public void AllowsReEncodingTopLevelPrimitiveFields() + { + var encoder = new MultipleVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.SetB("abc"); + encoder.SetC("def"); + encoder.A = 43; + encoder.CheckEncodingIsComplete(); + + var decoder = new MultipleVarLength(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(43, decoder.A); + Assert.AreEqual("abc", decoder.GetB()); + Assert.AreEqual("def", decoder.GetC()); + } + + [TestMethod] + public void DisallowsSkippingEncodingOfVariableLengthField1() + { + var encoder = new MultipleVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var exception = Assert.ThrowsException(() => encoder.SetC("def")); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"c\" in state: V0_BLOCK")); + } + + [TestMethod] + public void DisallowsSkippingEncodingOfVariableLengthField2() + { + var encoder = new MultipleVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var def = Encoding.ASCII.GetBytes("def"); + var exception = Assert.ThrowsException(() => encoder.SetC(def)); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"c\" in state: V0_BLOCK")); + } + + [TestMethod] + public void DisallowsReEncodingEarlierVariableLengthFields() + { + var encoder = new MultipleVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.SetB("abc"); + encoder.SetC("def"); + var exception = Assert.ThrowsException(() => encoder.SetB("ghi")); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b\" in state: V0_C_DONE")); + } + + [TestMethod] + public void DisallowsReEncodingLatestVariableLengthField() + { + var encoder = new MultipleVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.SetB("abc"); + encoder.SetC("def"); + var exception = Assert.ThrowsException(() => encoder.SetC("ghi")); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"c\" in state: V0_C_DONE")); + } + + [TestMethod] + public void DisallowsSkippingDecodingOfVariableLengthField1() + { + var decoder = DecodeUntilVarLengthFields(); + + var exception = Assert.ThrowsException(() => decoder.GetC()); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"c\" in state: V0_BLOCK")); + } + + [TestMethod] + public void DisallowsSkippingDecodingOfVariableLengthField2() + { + var decoder = DecodeUntilVarLengthFields(); + + var exception = Assert.ThrowsException(() => decoder.GetCBytes()); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"c\" in state: V0_BLOCK")); + } + + [TestMethod] + public void DisallowsSkippingDecodingOfVariableLengthField3() + { + var decoder = DecodeUntilVarLengthFields(); + + var exception = Assert.ThrowsException(() => decoder.GetC(new byte[8])); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"c\" in state: V0_BLOCK")); + } + + [TestMethod] + public void AllowsRepeatedDecodingOfVariableLengthDataLength() + { + var decoder = DecodeUntilVarLengthFields(); + Assert.AreEqual(3, decoder.BLength()); + Assert.AreEqual(3, decoder.BLength()); + Assert.AreEqual(3, decoder.BLength()); + Assert.AreEqual("abc", decoder.GetB()); + Assert.AreEqual(3, decoder.CLength()); + Assert.AreEqual(3, decoder.CLength()); + Assert.AreEqual(3, decoder.CLength()); + } + + [TestMethod] + public void DisallowsReDecodingEarlierVariableLengthField() + { + var decoder = DecodeUntilVarLengthFields(); + Assert.AreEqual("abc", decoder.GetB()); + Assert.AreEqual("def", decoder.GetC()); + + var exception = Assert.ThrowsException(() => decoder.GetB()); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b\" in state: V0_C_DONE")); + } + + [TestMethod] + public void DisallowsReDecodingLatestVariableLengthField() + { + var decoder = DecodeUntilVarLengthFields(); + Assert.AreEqual("abc", decoder.GetB()); + Assert.AreEqual("def", decoder.GetC()); + + var exception = Assert.ThrowsException(() => decoder.GetC()); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"c\" in state: V0_C_DONE")); + } + + private MultipleVarLength DecodeUntilVarLengthFields() + { + var encoder = new MultipleVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.SetB("abc"); + encoder.SetC("def"); + encoder.CheckEncodingIsComplete(); + + var decoder = new MultipleVarLength(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + + return decoder; + } + + [TestMethod] + public void AllowsEncodingAndDecodingGroupAndVariableLengthFieldsInSchemaDefinedOrder() + { + var encoder = new GroupAndVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2); + bEncoder.Next().C = 1; + bEncoder.Next().C = 2; + encoder.SetD("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new GroupAndVarLength(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(2, bDecoder.Count); + Assert.AreEqual(1, bDecoder.Next().C); + Assert.AreEqual(2, bDecoder.Next().C); + Assert.AreEqual("abc", decoder.GetD()); + Assert.IsTrue(decoder.ToString().Contains("A=42|B=[(C=1),(C=2)]|D='abc'")); + } + + [TestMethod] + public void AllowsEncodingAndDecodingEmptyGroupAndVariableLengthFieldsInSchemaDefinedOrder() + { + var encoder = new GroupAndVarLength() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.BCount(0); + encoder.SetD("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new GroupAndVarLength() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bs = decoder.B; + Assert.AreEqual(0, bs.Count); + Assert.AreEqual("abc", decoder.GetD()); + Assert.IsTrue(decoder.ToString().Contains("A=42|B=[]|D='abc'")); + } + + [TestMethod] + public void AllowsEncoderToResetZeroGroupLengthToZero() + { + var encoder = new GroupAndVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.BCount(0).ResetCountToIndex(); + encoder.SetD("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new GroupAndVarLength(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(0, bDecoder.Count); + Assert.AreEqual("abc", decoder.GetD()); + Assert.IsTrue(decoder.ToString().Contains("A=42|B=[]|D='abc'")); + } + + [TestMethod] + public void AllowsEncoderToResetNonZeroGroupLengthToZeroBeforeCallingNext() + { + var encoder = new GroupAndVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.BCount(2).ResetCountToIndex(); + encoder.SetD("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new GroupAndVarLength(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(0, bDecoder.Count); + Assert.AreEqual("abc", decoder.GetD()); + Assert.IsTrue(decoder.ToString().Contains("A=42|B=[]|D='abc'")); + } + + [TestMethod] + public void AllowsEncoderToResetNonZeroGroupLengthToNonZero() + { + var encoder = new GroupAndVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2); + bEncoder.Next().C = 43; + bEncoder.ResetCountToIndex(); + encoder.SetD("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new GroupAndVarLength(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(43, bDecoder.Next().C); + Assert.AreEqual("abc", decoder.GetD()); + Assert.IsTrue(decoder.ToString().Contains("A=42|B=[(C=43)]|D='abc'")); + } + + [TestMethod] + public void DisallowsEncoderToResetGroupLengthMidGroupElement() + { + var encoder = new NestedGroups(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2).Next(); + bEncoder.C = 43; + var exception = Assert.ThrowsException(() => bEncoder.ResetCountToIndex()); + Assert.IsTrue(exception.Message.Contains( + "Cannot reset count of repeating group \"b\" in state: V0_B_N_BLOCK")); + } + + [TestMethod] + public void DisallowsEncodingGroupElementBeforeCallingNext() + { + var encoder = new GroupAndVarLength() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(1); + var exception = Assert.ThrowsException(() => bEncoder.C = 1); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsDecodingGroupElementBeforeCallingNext() + { + var encoder = new GroupAndVarLength() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2); + bEncoder + .Next() + .C = 1; + bEncoder.Next() + .C = 2; + encoder.SetD("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new GroupAndVarLength() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bs = decoder.B; + Assert.AreEqual(2, bs.Count); + var exception = Assert.ThrowsException(() => _ = bs.C); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsSkippingEncodingOfGroup() + { + var encoder = new GroupAndVarLength() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var exception = Assert.ThrowsException(() => encoder.SetD("abc")); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"d\" in state: V0_BLOCK")); + } + + [TestMethod] + public void DisallowsReEncodingVariableLengthFieldAfterGroup() + { + var encoder = new GroupAndVarLength() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2); + bEncoder + .Next() + .C = 1; + bEncoder.Next() + .C = 2; + encoder.SetD("abc"); + var exception = Assert.ThrowsException(() => encoder.SetD("def")); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"d\" in state: V0_D_DONE")); + } + + [TestMethod] + public void DisallowsReEncodingGroupCount() + { + var encoder = new GroupAndVarLength() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2); + bEncoder + .Next() + .C = 1; + bEncoder.Next() + .C = 2; + encoder.SetD("abc"); + var exception = Assert.ThrowsException(() => encoder.BCount(1)); + Assert.IsTrue( + exception.Message.Contains("Cannot encode count of repeating group \"b\" in state: V0_D_DONE")); + } + + [TestMethod] + public void DisallowsMissedDecodingOfGroupBeforeVariableLengthField() + { + var encoder = new GroupAndVarLength() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2); + bEncoder + .Next() + .C = 1; + bEncoder + .Next() + .C = 2; + encoder.SetD("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new GroupAndVarLength() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var exception = Assert.ThrowsException(() => decoder.GetD()); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"d\" in state: V0_BLOCK")); + } + + [TestMethod] + public void DisallowsReDecodingVariableLengthFieldAfterGroup() + { + var encoder = new GroupAndVarLength() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2); + bEncoder + .Next() + .C = 1; + bEncoder + .Next() + .C = 2; + encoder.SetD("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new GroupAndVarLength() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bs = decoder.B; + Assert.AreEqual(2, bs.Count); + Assert.AreEqual(1, bs.Next().C); + Assert.AreEqual(2, bs.Next().C); + Assert.AreEqual("abc", decoder.GetD()); + var exception = Assert.ThrowsException(() => decoder.GetD()); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"d\" in state: V0_D_DONE")); + } + + [TestMethod] + public void DisallowsReDecodingGroupAfterVariableLengthField() + { + var encoder = new GroupAndVarLength() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2); + bEncoder + .Next() + .C = 1; + bEncoder + .Next() + .C = 2; + encoder.SetD("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new GroupAndVarLength() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bs = decoder.B; + Assert.AreEqual(2, bs.Count); + Assert.AreEqual(1, bs.Next().C); + Assert.AreEqual(2, bs.Next().C); + Assert.AreEqual("abc", decoder.GetD()); + var exception = Assert.ThrowsException(() => decoder.B); + Assert.IsTrue( + exception.Message.Contains("Cannot decode count of repeating group \"b\" in state: V0_D_DONE")); + } + + [TestMethod] + public void AllowsEncodingAndDecodingVariableLengthFieldInsideGroupInSchemaDefinedOrder() + { + var encoder = new VarLengthInsideGroup() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2); + bEncoder + .Next() + .C = 1; + bEncoder + .SetD("abc"); + bEncoder + .Next() + .C = 2; + bEncoder + .SetD("def"); + encoder.SetE("ghi"); + encoder.CheckEncodingIsComplete(); + + var decoder = new VarLengthInsideGroup() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bs = decoder.B; + Assert.AreEqual(2, bs.Count); + Assert.AreEqual(1, bs.Next().C); + Assert.AreEqual("abc", bs.GetD()); + Assert.AreEqual(2, bs.Next().C); + Assert.AreEqual("def", bs.GetD()); + Assert.AreEqual("ghi", decoder.GetE()); + Assert.IsTrue(decoder.ToString().Contains("A=42|B=[(C=1|D='abc'),(C=2|D='def')]|E='ghi'")); + } + + [TestMethod] + public void DisallowsMissedGroupElementVariableLengthFieldToEncodeAtTopLevel() + { + var encoder = new VarLengthInsideGroup() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.BCount(1).Next().C = 1; + var exception = Assert.ThrowsException(() => encoder.SetE("abc")); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"e\" in state: V0_B_1_BLOCK")); + } + + [TestMethod] + public void DisallowsMissedGroupElementVariableLengthFieldToEncodeNextElement() + { + var encoder = new VarLengthInsideGroup() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2).Next(); + var exception = Assert.ThrowsException(() => bEncoder.Next()); + Assert.IsTrue( + exception.Message.Contains( + "Cannot access next element in repeating group \"b\" in state: V0_B_N_BLOCK")); + } + + [TestMethod] + public void DisallowsMissedGroupElementEncoding() + { + var encoder = new VarLengthInsideGroup() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2); + bEncoder.Next().C = 1; + bEncoder.SetD("abc"); + var exception = Assert.ThrowsException(() => encoder.SetE("abc")); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"e\" in state: V0_B_N_D_DONE")); + } + + [TestMethod] + public void DisallowsReEncodingGroupElementVariableLengthField() + { + var encoder = new VarLengthInsideGroup() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(1).Next(); + bEncoder.C = 1; + bEncoder.SetD("abc"); + encoder.SetE("def"); + var exception = Assert.ThrowsException(() => bEncoder.SetD("ghi")); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.d\" in state: V0_E_DONE")); + } + + [TestMethod] + public void DisallowsReDecodingGroupElementVariableLengthField() + { + var encoder = new VarLengthInsideGroup() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2).Next(); + bEncoder.C = 1; + bEncoder.SetD("abc"); + bEncoder.Next().C = 2; + bEncoder.SetD("def"); + encoder.SetE("ghi"); + encoder.CheckEncodingIsComplete(); + + var decoder = new VarLengthInsideGroup() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(2, bDecoder.Count); + Assert.AreEqual(1, bDecoder.Next().C); + Assert.AreEqual("abc", bDecoder.GetD()); + var exception = Assert.ThrowsException(() => bDecoder.GetD()); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.d\" in state: V0_B_N_D_DONE")); + } + + [TestMethod] + public void DisallowsMissedDecodingOfGroupElementVariableLengthFieldToNextElement() + { + var encoder = new VarLengthInsideGroup() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2).Next(); + bEncoder.C = 1; + bEncoder.SetD("abc"); + bEncoder.Next().C = 2; + bEncoder.SetD("def"); + encoder.SetE("ghi"); + encoder.CheckEncodingIsComplete(); + + var decoder = new VarLengthInsideGroup() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(2, bDecoder.Count); + Assert.AreEqual(1, bDecoder.Next().C); + var exception = Assert.ThrowsException(() => bDecoder.Next()); + Assert.IsTrue( + exception.Message.Contains( + "Cannot access next element in repeating group \"b\" in state: V0_B_N_BLOCK")); + } + + [TestMethod] + public void DisallowsMissedDecodingOfGroupElementVariableLengthFieldToTopLevel() + { + var encoder = new VarLengthInsideGroup() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(1).Next(); + bEncoder.C = 1; + bEncoder.SetD("abc"); + encoder.SetE("ghi"); + encoder.CheckEncodingIsComplete(); + + var decoder = new VarLengthInsideGroup() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(1, bDecoder.Next().C); + var exception = Assert.ThrowsException(() => decoder.GetE()); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"e\" in state: V0_B_1_BLOCK")); + } + + [TestMethod] + public void DisallowsMissedDecodingOfGroupElement() + { + var encoder = new VarLengthInsideGroup() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2).Next(); + bEncoder.C = 1; + bEncoder.SetD("abc"); + bEncoder.Next().C = 2; + bEncoder.SetD("def"); + encoder.SetE("ghi"); + encoder.CheckEncodingIsComplete(); + + var decoder = new VarLengthInsideGroup() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(2, bDecoder.Count); + Assert.AreEqual(1, bDecoder.Next().C); + var exception = Assert.ThrowsException(() => decoder.GetE()); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"e\" in state: V0_B_N_BLOCK")); + } + + [TestMethod] + public void AllowsEncodingNestedGroupsInSchemaDefinedOrder() + { + var encoder = new NestedGroups().WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(2).Next(); + bEncoder.C = 1; + var dEncoder = bEncoder.DCount(2).Next(); + dEncoder.E = 2; + dEncoder = dEncoder.Next(); + dEncoder.E = 3; + var fEncoder = bEncoder.FCount(1).Next(); + fEncoder.G = 4; + bEncoder = bEncoder.Next(); + bEncoder.C = 5; + dEncoder = bEncoder.DCount(1).Next(); + dEncoder.E = 6; + fEncoder = bEncoder.FCount(1).Next(); + fEncoder.G = 7; + var hEncoder = encoder.HCount(1).Next(); + hEncoder.I = 8; + encoder.CheckEncodingIsComplete(); + + var decoder = new NestedGroups().WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(2, bDecoder.Count); + var b0 = bDecoder.Next(); + Assert.AreEqual(1, b0.C); + var b0ds = b0.D; + Assert.AreEqual(2, b0ds.Count); + Assert.AreEqual(2, b0ds.Next().E); + Assert.AreEqual(3, b0ds.Next().E); + var b0Fs = b0.F; + Assert.AreEqual(1, b0Fs.Count); + Assert.AreEqual(4, b0Fs.Next().G); + var b1 = bDecoder.Next(); + Assert.AreEqual(5, b1.C); + var b1ds = b1.D; + Assert.AreEqual(1, b1ds.Count); + Assert.AreEqual(6, b1ds.Next().E); + var b1Fs = b1.F; + Assert.AreEqual(1, b1Fs.Count); + Assert.AreEqual(7, b1Fs.Next().G); + var hs = decoder.H; + Assert.AreEqual(1, hs.Count); + Assert.AreEqual(8, hs.Next().I); + Assert.IsTrue(decoder.ToString() + .Contains("A=42|B=[(C=1|D=[(E=2),(E=3)]|F=[(G=4)]),(C=5|D=[(E=6)]|F=[(G=7)])]|H=[(I=8)]")); + } + + [TestMethod] + public void AllowsEncodingEmptyNestedGroupsInSchemaDefinedOrder() + { + var encoder = new NestedGroups().WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.BCount(0); + encoder.HCount(0); + encoder.CheckEncodingIsComplete(); + + var decoder = new NestedGroups().WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(0, bDecoder.Count); + var hDecoder = decoder.H; + Assert.AreEqual(0, hDecoder.Count); + Assert.IsTrue(decoder.ToString().Contains("A=42|B=[]|H=[]")); + } + + [TestMethod] + public void DisallowsMissedEncodingOfNestedGroup() + { + var encoder = new NestedGroups().WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(1).Next(); + bEncoder.C = 1; + Exception exception = Assert.ThrowsException(() => bEncoder.FCount(1)); + Assert.IsTrue( + exception.Message.Contains("Cannot encode count of repeating group \"b.f\" in state: V0_B_1_BLOCK")); + } + + [TestMethod] + public void AllowsEncodingAndDecodingCompositeInsideGroupInSchemaDefinedOrder() + { + var encoder = new CompositeInsideGroup().WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + var aEncoder = encoder.A; + aEncoder.X = 1; + aEncoder.Y = 2; + var bEncoder = encoder.BCount(1).Next(); + var cEncoder = bEncoder.C; + cEncoder.X = 3; + cEncoder.Y = 4; + encoder.CheckEncodingIsComplete(); + + var decoder = new CompositeInsideGroup().WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + var aDecoder = decoder.A; + Assert.AreEqual(1, aDecoder.X); + Assert.AreEqual(2, aDecoder.Y); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + var cDecoder = bDecoder.Next().C; + Assert.AreEqual(3, cDecoder.X); + Assert.AreEqual(4, cDecoder.Y); + Assert.IsTrue(decoder.ToString().Contains("A=(X=1|Y=2)|B=[(C=(X=3|Y=4))]")); + } + + [TestMethod] + public void DisallowsEncodingCompositeInsideGroupBeforeCallingNext() + { + var encoder = new CompositeInsideGroup().WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + var aEncoder = encoder.A; + aEncoder.X = 1; + aEncoder.Y = 2; + var bEncoder = encoder.BCount(1); + Exception exception = Assert.ThrowsException(() => bEncoder.C); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void AllowsReEncodingTopLevelCompositeViaReWrap() + { + var encoder = new CompositeInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + var aEncoder = encoder.A; + aEncoder.X = 1; + aEncoder.Y = 2; + var bEncoder = encoder.BCount(1).Next(); + bEncoder.C.X = 3; + bEncoder.C.Y = 4; + aEncoder = encoder.A; + aEncoder.X = 5; + aEncoder.Y = 6; + encoder.CheckEncodingIsComplete(); + + var decoder = new CompositeInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + var a = decoder.A; + Assert.AreEqual(5, a.X); + Assert.AreEqual(6, a.Y); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + var c = b.Next().C; + Assert.AreEqual(3, c.X); + Assert.AreEqual(4, c.Y); + } + + [TestMethod] + public void AllowsReEncodingTopLevelCompositeViaEncoderReference() + { + var encoder = new CompositeInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + var aEncoder = encoder.A; + aEncoder.X = 1; + aEncoder.Y = 2; + var bEncoder = encoder.BCount(1).Next(); + bEncoder.C.X = 3; + bEncoder.C.Y = 4; + aEncoder.X = 5; + aEncoder.Y = 6; + encoder.CheckEncodingIsComplete(); + + var decoder = new CompositeInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + var a = decoder.A; + Assert.AreEqual(5, a.X); + Assert.AreEqual(6, a.Y); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + var c = b.Next().C; + Assert.AreEqual(3, c.X); + Assert.AreEqual(4, c.Y); + } + + [TestMethod] + public void AllowsReEncodingGroupElementCompositeViaReWrap() + { + var encoder = new CompositeInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + var aEncoder = encoder.A; + aEncoder.X = 1; + aEncoder.Y = 2; + var bEncoder = encoder.BCount(1).Next(); + var cEncoder = bEncoder.C; + cEncoder.X = 3; + cEncoder.Y = 4; + cEncoder = bEncoder.C; + cEncoder.X = 5; + cEncoder.Y = 6; + encoder.CheckEncodingIsComplete(); + + var decoder = new CompositeInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + var a = decoder.A; + Assert.AreEqual(1, a.X); + Assert.AreEqual(2, a.Y); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + var c = b.Next().C; + Assert.AreEqual(5, c.X); + Assert.AreEqual(6, c.Y); + } + + [TestMethod] + public void AllowsReEncodingGroupElementCompositeViaEncoderReference() + { + var encoder = new CompositeInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + var aEncoder = encoder.A; + aEncoder.X = 1; + aEncoder.Y = 2; + var bEncoder = encoder.BCount(1).Next(); + var cEncoder = bEncoder.C; + cEncoder.X = 3; + cEncoder.Y = 4; + cEncoder.X = 5; + cEncoder.Y = 6; + encoder.CheckEncodingIsComplete(); + + var decoder = new CompositeInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + var a = decoder.A; + Assert.AreEqual(1, a.X); + Assert.AreEqual(2, a.Y); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + var c = b.Next().C; + Assert.AreEqual(5, c.X); + Assert.AreEqual(6, c.Y); + } + + [TestMethod] + public void AllowsReDecodingTopLevelCompositeViaReWrap() + { + var encoder = new CompositeInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + var aEncoder = encoder.A; + aEncoder.X = 1; + aEncoder.Y = 2; + var bEncoder = encoder.BCount(1).Next(); + var cEncoder = bEncoder.C; + cEncoder.X = 3; + cEncoder.Y = 4; + encoder.CheckEncodingIsComplete(); + + var decoder = new CompositeInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + var a1 = decoder.A; + Assert.AreEqual(1, a1.X); + Assert.AreEqual(2, a1.Y); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + var c = b.Next().C; + Assert.AreEqual(3, c.X); + Assert.AreEqual(4, c.Y); + var a2 = decoder.A; + Assert.AreEqual(1, a2.X); + Assert.AreEqual(2, a2.Y); + } + + [TestMethod] + public void AllowsReDecodingTopLevelCompositeViaEncoderReference() + { + var encoder = new CompositeInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + var aEncoder = encoder.A; + aEncoder.X = 1; + aEncoder.Y = 2; + var bEncoder = encoder.BCount(1).Next(); + var cEncoder = bEncoder.C; + cEncoder.X = 3; + cEncoder.Y = 4; + encoder.CheckEncodingIsComplete(); + + var decoder = new CompositeInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + var a = decoder.A; + Assert.AreEqual(1, a.X); + Assert.AreEqual(2, a.Y); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + var c = b.Next().C; + Assert.AreEqual(3, c.X); + Assert.AreEqual(4, c.Y); + Assert.AreEqual(1, a.X); + Assert.AreEqual(2, a.Y); + } + + [TestMethod] + public void AllowsReDecodingGroupElementCompositeViaEncoderReference() + { + var encoder = new CompositeInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + var aEncoder = encoder.A; + aEncoder.X = 1; + aEncoder.Y = 2; + var bEncoder = encoder.BCount(1).Next(); + var cEncoder = bEncoder.C; + cEncoder.X = 3; + cEncoder.Y = 4; + encoder.CheckEncodingIsComplete(); + + var decoder = new CompositeInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + var a = decoder.A; + Assert.AreEqual(1, a.X); + Assert.AreEqual(2, a.Y); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + var c = b.Next().C; + Assert.AreEqual(3, c.X); + Assert.AreEqual(4, c.Y); + Assert.AreEqual(3, c.X); + Assert.AreEqual(4, c.Y); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeAddedPrimitiveField() + { + var encoder = new AddPrimitiveV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.B = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddPrimitiveV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual(2, decoder.B); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingPrimitiveFieldAsNullValue() + { + var encoder = new AddPrimitiveV0() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddPrimitiveV1() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual(AddPrimitiveV1.BNullValue, decoder.B); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeAddedPrimitiveFieldBeforeGroup() + { + var encoder = new AddPrimitiveBeforeGroupV1() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.D = 3; + encoder.BCount(1).Next().C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddPrimitiveBeforeGroupV1() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual(3, decoder.D); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + Assert.AreEqual(2, b.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingPrimitiveFieldBeforeGroupAsNullValue() + { + var encoder = new AddPrimitiveBeforeGroupV0() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.BCount(1).Next().C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddPrimitiveBeforeGroupV1() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual(AddPrimitiveBeforeGroupV1.DNullValue, decoder.D); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + Assert.AreEqual(2, b.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToSkipPresentButAddedPrimitiveFieldBeforeGroup() + { + var encoder = new AddPrimitiveBeforeGroupV1() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.D = 3; + encoder.BCount(1).Next().C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddPrimitiveBeforeGroupV1() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + Assert.AreEqual(2, b.Next().C); + } + + [TestMethod] + public void AllowsOldDecoderToSkipAddedPrimitiveFieldBeforeGroup() + { + var encoder = new AddPrimitiveBeforeGroupV1() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.D = 3; + encoder.BCount(1).Next().C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion1(); + + var decoder = new AddPrimitiveBeforeGroupV0() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + Assert.AreEqual(2, b.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeAddedPrimitiveFieldBeforeVarData() + { + var encoder = new AddPrimitiveBeforeVarDataV1() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.C = 3; + encoder.SetB("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new AddPrimitiveBeforeVarDataV1() + .WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual(3, decoder.C); + Assert.AreEqual("abc", decoder.GetB()); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingPrimitiveFieldBeforeVarDataAsNullValue() + { + var encoder = new AddPrimitiveBeforeVarDataV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.SetB("abc"); + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddPrimitiveBeforeVarDataV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual(AddPrimitiveBeforeVarDataV1.CNullValue, decoder.C); + Assert.AreEqual("abc", decoder.GetB()); + } + + [TestMethod] + public void AllowsNewDecoderToSkipPresentButAddedPrimitiveFieldBeforeVarData() + { + var encoder = new AddPrimitiveBeforeVarDataV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.C = 3; + encoder.SetB("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new AddPrimitiveBeforeVarDataV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual("abc", decoder.GetB()); + } + + [TestMethod] + public void AllowsOldDecoderToSkipAddedPrimitiveFieldBeforeVarData() + { + var encoder = new AddPrimitiveBeforeVarDataV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.C = 3; + encoder.SetB("abc"); + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion1(); + + var decoder = new AddPrimitiveBeforeVarDataV0(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual("abc", decoder.GetB()); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeAddedPrimitiveFieldInsideGroup() + { + var encoder = new AddPrimitiveInsideGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var group1 = encoder.BCount(1); + group1.Next(); + group1.C = 2; + group1.D = 3; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddPrimitiveInsideGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var group2 = decoder.B; + Assert.AreEqual(1, group2.Count); + Assert.AreEqual(2, group2.Next().C); + Assert.AreEqual(3, group2.D); + } + + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingPrimitiveFieldInsideGroupAsNullValue() + { + var encoder = new AddPrimitiveInsideGroupV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.BCount(1).Next().C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddPrimitiveInsideGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + Assert.AreEqual(2, b.Next().C); + Assert.AreEqual(AddPrimitiveInsideGroupV1.BGroup.DNullValue, b.D); + } + + [TestMethod] + public void AllowsNewDecoderToSkipPresentButAddedPrimitiveFieldInsideGroup() + { + var encoder = new AddPrimitiveInsideGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var group = encoder.BCount(2); + group.Next().C = 2; + group.D = 3; + group.Next().C = 4; + group.D = 5; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddPrimitiveInsideGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var b = decoder.B; + Assert.AreEqual(2, b.Count); + Assert.AreEqual(2, b.Next().C); + Assert.AreEqual(4, b.Next().C); + } + + [TestMethod] + public void AllowsOldDecoderToSkipAddedPrimitiveFieldInsideGroup() + { + var encoder = new AddPrimitiveInsideGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var group = encoder.BCount(2); + group.Next().C = 2; + group.D = 3; + group.Next().C = 4; + group.D = 5; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion1(); + + var decoder = new AddPrimitiveInsideGroupV0(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var b = decoder.B; + Assert.AreEqual(2, b.Count); + Assert.AreEqual(2, b.Next().C); + Assert.AreEqual(4, b.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeAddedGroupBeforeVarData() + { + var encoder = new AddGroupBeforeVarDataV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var groupEncoder = encoder.CCount(1); + groupEncoder.Next().D = 2; + encoder.SetB("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new AddGroupBeforeVarDataV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var groupDecoder = decoder.C; + Assert.AreEqual(1, groupDecoder.Count); + Assert.AreEqual(2, groupDecoder.Next().D); + Assert.AreEqual("abc", decoder.GetB()); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingGroupBeforeVarDataAsNullValue() + { + var encoder = new AddGroupBeforeVarDataV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.SetB("abc"); + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddGroupBeforeVarDataV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var groupDecoder = decoder.C; + Assert.AreEqual(0, groupDecoder.Count); + Assert.AreEqual("abc", decoder.GetB()); + Assert.IsTrue(decoder.ToString().Contains("A=1|C=[]|B='abc'")); + } + + [TestMethod] + public void AllowsNewDecoderToSkipMissingGroupBeforeVarData() + { + var encoder = new AddGroupBeforeVarDataV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.SetB("abc"); + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddGroupBeforeVarDataV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual("abc", decoder.GetB()); + } + + [TestMethod] + public void DisallowsNewDecoderToSkipPresentButAddedGroupBeforeVarData() + { + var encoder = new AddGroupBeforeVarDataV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var groupEncoder = encoder.CCount(1); + groupEncoder.Next().D = 2; + encoder.SetB("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new AddGroupBeforeVarDataV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + + var exception = Assert.ThrowsException(() => decoder.GetB()); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b\" in state: V1_BLOCK")); + } + + [TestMethod] + public void AllowsOldDecoderToSkipAddedGroupBeforeVarData() + { + var encoder = new AddGroupBeforeVarDataV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + _messageHeader.NumGroups = 1; + encoder.A = 1; + var groupEncoder = encoder.CCount(1); + groupEncoder.Next().D = 2; + encoder.SetB("abc"); + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion1(); + + var decoder = new AddGroupBeforeVarDataV0(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + + for (int i = 0; i < _messageHeader.NumGroups; i++) + { + SkipGroup(decoder); + } + + Assert.AreEqual("abc", decoder.GetB()); + } + + private void SkipGroup(AddGroupBeforeVarDataV0 decoder) + { + var groupSizeEncoding = new GroupSizeEncoding(); + groupSizeEncoding.Wrap(_buffer, decoder.Limit, MessageHeader.SbeSchemaVersion); + var bytesToSkip = GroupSizeEncoding.Size + + groupSizeEncoding.BlockLength * groupSizeEncoding.NumInGroup; + decoder.Limit += bytesToSkip; + } + + [TestMethod] + public void AllowsNewDecoderToDecodeAddedEnumFieldBeforeGroup() + { + var encoder = new AddEnumBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.D = Direction.BUY; + var groupEncoder = encoder.BCount(1); + groupEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddEnumBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual(Direction.BUY, decoder.D); + var groupDecoder = decoder.B; + Assert.AreEqual(1, groupDecoder.Count); + Assert.AreEqual(2, groupDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingEnumFieldBeforeGroupAsNullValue() + { + var encoder = new AddEnumBeforeGroupV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var myGroup = encoder.BCount(1); + myGroup.Next(); + myGroup.C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddEnumBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual(Direction.NULL_VALUE, decoder.D); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + Assert.AreEqual(2, b.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToSkipPresentButAddedEnumFieldBeforeGroup() + { + var encoder = new AddEnumBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.D = Direction.SELL; + encoder.BCount(1).Next().C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddEnumBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + Assert.AreEqual(2, b.Next().C); + } + + [TestMethod] + public void AllowsOldDecoderToSkipAddedEnumFieldBeforeGroup() + { + var encoder = new AddEnumBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.D = Direction.BUY; + encoder.BCount(1).Next().C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion1(); + + var decoder = new AddEnumBeforeGroupV0(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var b = decoder.B; + Assert.AreEqual(1, b.Count); + Assert.AreEqual(2, b.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeAddedCompositeFieldBeforeGroup() + { + var encoder = new AddCompositeBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var dEncoder = encoder.D; + dEncoder.X = -1; + dEncoder.Y = -2; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddCompositeBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var d = decoder.D; + Assert.IsNotNull(d); + Assert.AreEqual(-1, d.X); + Assert.AreEqual(-2, d.Y); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingCompositeFieldBeforeGroupAsNullValue() + { + var encoder = new AddCompositeBeforeGroupV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddCompositeBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.IsNull(decoder.D); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToSkipPresentButAddedCompositeFieldBeforeGroup() + { + var encoder = new AddCompositeBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var dEncoder = encoder.D; + dEncoder.X = -1; + dEncoder.Y = -2; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddCompositeBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsOldDecoderToSkipAddedCompositeFieldBeforeGroup() + { + var encoder = new AddCompositeBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var dEncoder = encoder.D; + dEncoder.X = -1; + dEncoder.Y = -2; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion1(); + + var decoder = new AddCompositeBeforeGroupV0(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeAddedArrayFieldBeforeGroup1() + { + var encoder = new AddArrayBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var dEncoder = encoder.DAsSpan(); + dEncoder[0] = 1; + dEncoder[1] = 2; + dEncoder[2] = 3; + dEncoder[3] = 4; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddArrayBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual((short)1, decoder.GetD(0)); + Assert.AreEqual((short)2, decoder.GetD(1)); + Assert.AreEqual((short)3, decoder.GetD(2)); + Assert.AreEqual((short)4, decoder.GetD(3)); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeAddedArrayFieldBeforeGroup2() + { + var encoder = new AddArrayBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.SetD(0, 1); + encoder.SetD(1, 2); + encoder.SetD(2, 3); + encoder.SetD(3, 4); + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddArrayBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual((short)1, decoder.GetD(0)); + Assert.AreEqual((short)2, decoder.GetD(1)); + Assert.AreEqual((short)3, decoder.GetD(2)); + Assert.AreEqual((short)4, decoder.GetD(3)); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingArrayFieldBeforeGroupAsNullValue1() + { + var encoder = new AddArrayBeforeGroupV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddArrayBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual(AddArrayBeforeGroupV1.DNullValue, decoder.GetD(0)); + Assert.AreEqual(AddArrayBeforeGroupV1.DNullValue, decoder.GetD(1)); + Assert.AreEqual(AddArrayBeforeGroupV1.DNullValue, decoder.GetD(2)); + Assert.AreEqual(AddArrayBeforeGroupV1.DNullValue, decoder.GetD(3)); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingArrayFieldBeforeGroupAsNullValue2() + { + var encoder = new AddArrayBeforeGroupV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddArrayBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual(0, decoder.D.Length); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToSkipPresentButAddedArrayFieldBeforeGroup() + { + var encoder = new AddArrayBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.SetD(0, 1); + encoder.SetD(1, 2); + encoder.SetD(2, 3); + encoder.SetD(3, 4); + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddArrayBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsOldDecoderToSkipAddedArrayFieldBeforeGroup() + { + var encoder = new AddArrayBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.SetD(0, 1); + encoder.SetD(1, 2); + encoder.SetD(2, 3); + encoder.SetD(3, 4); + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion1(); + + var decoder = new AddArrayBeforeGroupV0(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeAddedBitSetFieldBeforeGroup() + { + var encoder = new AddBitSetBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.D = Flags.Guacamole | Flags.Cheese; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddBitSetBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.IsNotNull(decoder.D); + Assert.AreEqual(true, decoder.D.HasFlag(Flags.Guacamole)); + Assert.AreEqual(true, decoder.D.HasFlag(Flags.Cheese)); + Assert.AreEqual(false, decoder.D.HasFlag(Flags.SourCream)); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingBitSetFieldBeforeGroupAsNullValue() + { + var encoder = new AddBitSetBeforeGroupV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddBitSetBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual((byte)decoder.D, 0); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToSkipPresentButAddedBitSetFieldBeforeGroup() + { + var encoder = new AddBitSetBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.D = Flags.Guacamole | Flags.Cheese; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddBitSetBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsOldDecoderToSkipAddedBitSetFieldBeforeGroup() + { + var encoder = new AddBitSetBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.D = Flags.Guacamole | Flags.Cheese; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion1(); + + var decoder = new AddBitSetBeforeGroupV0(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsEncodingAndDecodingEnumInsideGroupInSchemaDefinedOrder() + { + var encoder = new EnumInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = Direction.BUY; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = Direction.SELL; + encoder.CheckEncodingIsComplete(); + + var decoder = new EnumInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(Direction.BUY, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(Direction.SELL, bDecoder.Next().C); + Assert.IsTrue(decoder.ToString().Contains("A=BUY|B=[(C=SELL)]")); + } + + [TestMethod] + public void DisallowsEncodingEnumInsideGroupBeforeCallingNext() + { + var encoder = new EnumInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = Direction.BUY; + var bEncoder = encoder.BCount(1); + var exception = Assert.ThrowsException(() => bEncoder.C = Direction.SELL); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsDecodingEnumInsideGroupBeforeCallingNext() + { + var encoder = new EnumInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = Direction.BUY; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = Direction.SELL; + encoder.CheckEncodingIsComplete(); + + var decoder = new EnumInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(Direction.BUY, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + var exception = Assert.ThrowsException(() => + { + var _ = bDecoder.C; + }); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void AllowsReEncodingTopLevelEnum() + { + var encoder = new EnumInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = Direction.BUY; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = Direction.SELL; + + encoder.A = Direction.SELL; + encoder.CheckEncodingIsComplete(); + + var decoder = new EnumInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(Direction.SELL, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(Direction.SELL, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsEncodingAndDecodingBitSetInsideGroupInSchemaDefinedOrder() + { + var encoder = new BitSetInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = Flags.Cheese | Flags.Guacamole; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = Flags.SourCream; + encoder.CheckEncodingIsComplete(); + + var decoder = new BitSetInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(true, decoder.A.HasFlag((Flags.Guacamole))); + Assert.AreEqual(true, decoder.A.HasFlag(Flags.Cheese)); + Assert.AreEqual(false, decoder.A.HasFlag(Flags.SourCream)); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + var cDecoder = bDecoder.Next().C; + Assert.AreEqual(false, cDecoder.HasFlag((Flags.Guacamole))); + Assert.AreEqual(false, cDecoder.HasFlag(Flags.Cheese)); + Assert.AreEqual(true, cDecoder.HasFlag(Flags.SourCream)); + Assert.IsTrue(decoder.ToString().Contains("A={Guacamole, Cheese}|B=[(C={SourCream})]")); + } + + [TestMethod] + public void DisallowsEncodingBitSetInsideGroupBeforeCallingNext() + { + var encoder = new BitSetInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = Flags.Cheese | Flags.Guacamole; + var bEncoder = encoder.BCount(1); + var exception = Assert.ThrowsException(() => + { + var _ = bEncoder.C; + }); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsDecodingBitSetInsideGroupBeforeCallingNext() + { + var encoder = new BitSetInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = Flags.Cheese | Flags.Guacamole; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = Flags.SourCream; + encoder.CheckEncodingIsComplete(); + + var decoder = new BitSetInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(true, decoder.A.HasFlag((Flags.Guacamole))); + Assert.AreEqual(true, decoder.A.HasFlag(Flags.Cheese)); + Assert.AreEqual(false, decoder.A.HasFlag(Flags.SourCream)); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + var exception = Assert.ThrowsException(() => + { + var _ = bDecoder.C; + }); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void AllowsReEncodingTopLevelBitSetViaReWrap() + { + var encoder = new BitSetInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = Flags.Cheese | Flags.Guacamole; + var bEncoder = encoder.BCount(1); + bEncoder.Next().C = Flags.SourCream; + + encoder.A |= Flags.SourCream; + encoder.CheckEncodingIsComplete(); + + var decoder = new BitSetInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(true, decoder.A.HasFlag((Flags.Guacamole))); + Assert.AreEqual(true, decoder.A.HasFlag(Flags.Cheese)); + Assert.AreEqual(true, decoder.A.HasFlag(Flags.SourCream)); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + var cDecoder = bDecoder.Next().C; + Assert.AreEqual(false, cDecoder.HasFlag((Flags.Guacamole))); + Assert.AreEqual(false, cDecoder.HasFlag(Flags.Cheese)); + Assert.AreEqual(true, cDecoder.HasFlag(Flags.SourCream)); + } + + [TestMethod] + public void AllowsEncodingAndDecodingArrayInsideGroupInSchemaDefinedOrder() + { + var encoder = new ArrayInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.SetA(0, 1); + encoder.SetA(1, 2); + encoder.SetA(2, 3); + encoder.SetA(3, 4); + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.SetC(0, 5); + bEncoder.SetC(1, 6); + bEncoder.SetC(2, 7); + bEncoder.SetC(3, 8); + encoder.CheckEncodingIsComplete(); + + var decoder = new ArrayInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A[0]); + Assert.AreEqual(2, decoder.A[1]); + Assert.AreEqual(3, decoder.A[2]); + Assert.AreEqual(4, decoder.A[3]); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + bDecoder.Next(); + Assert.AreEqual(5, bDecoder.C[0]); + Assert.AreEqual(6, bDecoder.C[1]); + Assert.AreEqual(7, bDecoder.C[2]); + Assert.AreEqual(8, bDecoder.C[3]); + StringAssert.Contains(decoder.ToString(), "A=[1,2,3,4]|B=[(C=[5,6,7,8])]"); + } + + [TestMethod] + public void DisallowsEncodingArrayInsideGroupBeforeCallingNext1() + { + var bEncoder = EncodeUntilGroupWithArrayInside(); + var exception = Assert.ThrowsException(() => { bEncoder.SetC(0, 5); }); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsEncodingArrayInsideGroupBeforeCallingNext2() + { + var bEncoder = EncodeUntilGroupWithArrayInside(); + var exception = Assert.ThrowsException(() => { bEncoder.CAsSpan()[0] = 1; }); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + private ArrayInsideGroup.BGroup EncodeUntilGroupWithArrayInside() + { + var encoder = new ArrayInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.SetA(0, 1); + encoder.SetA(1, 2); + encoder.SetA(2, 3); + encoder.SetA(3, 4); + return encoder.BCount(1); + } + + [TestMethod] + public void DisallowsDecodingArrayInsideGroupBeforeCallingNext1() + { + var bDecoder = DecodeUntilGroupWithArrayInside(); + var exception = Assert.ThrowsException(() => bDecoder.C[0]); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsDecodingArrayInsideGroupBeforeCallingNext2() + { + var bDecoder = DecodeUntilGroupWithArrayInside(); + var exception = Assert.ThrowsException(() => bDecoder.GetC(0)); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + private ArrayInsideGroup.BGroup DecodeUntilGroupWithArrayInside() + { + var encoder = new ArrayInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.SetA(0, 1); + encoder.SetA(1, 2); + encoder.SetA(2, 3); + encoder.SetA(3, 4); + var bEncoder = encoder.BCount(1).Next(); + bEncoder.SetC(0, 5); + bEncoder.SetC(1, 6); + bEncoder.SetC(2, 7); + bEncoder.SetC(3, 8); + + encoder.CheckEncodingIsComplete(); + + var decoder = new ArrayInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual((short)1, decoder.A[0]); + Assert.AreEqual((short)2, decoder.A[1]); + Assert.AreEqual((short)3, decoder.A[2]); + Assert.AreEqual((short)4, decoder.A[3]); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + return bDecoder; + } + + [TestMethod] + public void AllowsReEncodingTopLevelArrayViaReWrap() + { + var encoder = new ArrayInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.SetA(0, 1); + encoder.SetA(1, 2); + encoder.SetA(2, 3); + encoder.SetA(3, 4); + var bEncoder = encoder.BCount(1).Next(); + bEncoder.SetC(0, 5); + bEncoder.SetC(1, 6); + bEncoder.SetC(2, 7); + bEncoder.SetC(3, 8); + + encoder.SetA(0, 9); + encoder.SetA(1, 10); + encoder.SetA(2, 11); + encoder.SetA(3, 12); + encoder.CheckEncodingIsComplete(); + + var decoder = new ArrayInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(9, decoder.A[0]); + Assert.AreEqual(10, decoder.A[1]); + Assert.AreEqual(11, decoder.A[2]); + Assert.AreEqual(12, decoder.A[3]); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + bDecoder.Next(); + Assert.AreEqual(5, bDecoder.C[0]); + Assert.AreEqual(6, bDecoder.C[1]); + Assert.AreEqual(7, bDecoder.C[2]); + Assert.AreEqual(8, bDecoder.C[3]); + } + + [TestMethod] + public void AllowsEncodingAndDecodingGroupFieldsInSchemaDefinedOrder1() + { + var encoder = new MultipleGroups(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.BCount(0); + var dEncoder = encoder.DCount(1).Next(); + dEncoder.E = 43; + encoder.CheckEncodingIsComplete(); + + var decoder = new MultipleGroups(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + Assert.AreEqual(0, decoder.B.Count); + var dDecoder = decoder.D; + Assert.AreEqual(1, dDecoder.Count); + Assert.AreEqual(dDecoder.Next().E, 43); + Assert.IsTrue(decoder.ToString().Contains("A=42|B=[]|D=[(E=43)]")); + } + + [TestMethod] + public void AllowsEncodingAndDecodingGroupFieldsInSchemaDefinedOrder2() + { + var encoder = new MultipleGroups(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 41; + var bEncoder = encoder.BCount(1).Next(); + bEncoder.C = 42; + var dEncoder = encoder.DCount(1).Next(); + dEncoder.E = 43; + encoder.CheckEncodingIsComplete(); + + var decoder = new MultipleGroups(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(41, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(42, bDecoder.Next().C); + var dDecoder = decoder.D; + Assert.AreEqual(1, dDecoder.Count); + Assert.AreEqual(43, dDecoder.Next().E); + } + + [TestMethod] + public void AllowsReEncodingTopLevelPrimitiveFieldsAfterGroups() + { + var encoder = new MultipleGroups(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 41; + var bEncoder = encoder.BCount(1).Next(); + bEncoder.C = 42; + var dEncoder = encoder.DCount(1).Next(); + dEncoder.E = 43; + encoder.A = 44; + encoder.CheckEncodingIsComplete(); + + var decoder = new MultipleGroups(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(44, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(42, bDecoder.Next().C); + var dDecoder = decoder.D; + Assert.AreEqual(1, dDecoder.Count); + Assert.AreEqual(43, dDecoder.Next().E); + } + + [TestMethod] + public void DisallowsMissedEncodingOfGroupField() + { + var encoder = new MultipleGroups(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 41; + Assert.ThrowsException(() => encoder.DCount(0)); + } + + [TestMethod] + public void DisallowsReEncodingEarlierGroupFields() + { + var encoder = new MultipleGroups(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 41; + var bEncoder = encoder.BCount(1).Next(); + bEncoder.C = 42; + var dEncoder = encoder.DCount(1).Next(); + dEncoder.E = 43; + Assert.ThrowsException(() => encoder.BCount(1)); + } + + [TestMethod] + public void DisallowsReEncodingLatestGroupField() + { + var encoder = new MultipleGroups(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 41; + var bEncoder = encoder.BCount(1).Next(); + bEncoder.C = 42; + var dEncoder = encoder.DCount(1).Next(); + dEncoder.E = 43; + Assert.ThrowsException(() => encoder.DCount(1)); + } + + [TestMethod] + public void DisallowsMissedDecodingOfGroupField() + { + var encoder = new MultipleGroups(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 41; + var bEncoder = encoder.BCount(1).Next(); + bEncoder.C = 42; + var dEncoder = encoder.DCount(1).Next(); + dEncoder.E = 43; + encoder.CheckEncodingIsComplete(); + + var decoder = new MultipleGroups(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(41, decoder.A); + Assert.ThrowsException(() => + { + // ReSharper disable once UnusedVariable + var ignored = decoder.D; + }); + } + + [TestMethod] + public void DisallowsReDecodingEarlierGroupField() + { + var encoder = new MultipleGroups(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 41; + var bEncoder = encoder.BCount(1).Next(); + bEncoder.C = 42; + var dEncoder = encoder.DCount(1).Next(); + dEncoder.E = 43; + encoder.CheckEncodingIsComplete(); + + var decoder = new MultipleGroups(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(41, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(42, bDecoder.Next().C); + var dDecoder = decoder.D; + Assert.AreEqual(1, dDecoder.Count); + Assert.AreEqual(43, dDecoder.Next().E); + Assert.ThrowsException(() => + { + // ReSharper disable once UnusedVariable + var ignored = decoder.B; + }); + } + + [TestMethod] + public void DisallowsReDecodingLatestGroupField() + { + var encoder = new MultipleGroups(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 41; + var bEncoder = encoder.BCount(1).Next(); + bEncoder.C = 42; + var dEncoder = encoder.DCount(1).Next(); + dEncoder.E = 43; + encoder.CheckEncodingIsComplete(); + + var decoder = new MultipleGroups(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(41, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(42, bDecoder.Next().C); + var dDecoder = decoder.D; + Assert.AreEqual(1, dDecoder.Count); + Assert.AreEqual(43, dDecoder.Next().E); + Assert.ThrowsException(() => + { + // ReSharper disable once UnusedVariable + var ignored = decoder.D; + }); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeAddedVarData() + { + var encoder = new AddVarDataV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.SetB("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new AddVarDataV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + Assert.AreEqual("abc", decoder.GetB()); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingAddedVarDataAsNullValue1() + { + var encoder = new AddVarDataV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddVarDataV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + Assert.AreEqual("", decoder.GetB()); + Assert.IsTrue(decoder.ToString().Contains("A=42|B=''")); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingAddedVarDataAsNullValue2() + { + var encoder = new AddVarDataV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddVarDataV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + Assert.AreEqual(0, decoder.BLength()); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingAddedVarDataAsNullValue3() + { + var encoder = new AddVarDataV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddVarDataV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + Assert.AreEqual(0, decoder.GetB(new byte[16])); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingAddedVarDataAsNullValue4() + { + var encoder = new AddVarDataV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddVarDataV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(42, decoder.A); + Assert.AreEqual(0, decoder.GetB(new byte[16], 0, 16)); + } + + [TestMethod] + public void AllowsEncodingAndDecodingAsciiInsideGroupInSchemaDefinedOrder1() + { + var encoder = new AsciiInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.SetA("GBPUSD"); + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.SetC("EURUSD"); + encoder.CheckEncodingIsComplete(); + + var decoder = new AsciiInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual("GBPUSD", decoder.GetA()); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + bDecoder.Next(); + Assert.AreEqual("EURUSD", bDecoder.GetC()); + Assert.IsTrue(decoder.ToString().Contains("A='GBPUSD'|B=[(C='EURUSD')]")); + } + + [TestMethod] + public void AllowsEncodingAndDecodingAsciiInsideGroupInSchemaDefinedOrder2() + { + var encoder = new AsciiInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + var gbpUsdBytes = Encoding.ASCII.GetBytes("GBPUSD"); + encoder.SetA(gbpUsdBytes, 0); + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.SetC(Encoding.ASCII.GetBytes("EURUSD"), 0); + encoder.CheckEncodingIsComplete(); + + var decoder = new AsciiInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + var aBytes = new byte[6]; + decoder.GetA(aBytes, 0); + Assert.IsTrue(gbpUsdBytes.SequenceEqual(aBytes)); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + bDecoder.Next(); + Assert.AreEqual((byte)'E', bDecoder.GetC(0)); + Assert.AreEqual((byte)'U', bDecoder.GetC(1)); + Assert.AreEqual((byte)'R', bDecoder.GetC(2)); + Assert.AreEqual((byte)'U', bDecoder.GetC(3)); + Assert.AreEqual((byte)'S', bDecoder.GetC(4)); + Assert.AreEqual((byte)'D', bDecoder.GetC(5)); + } + + [TestMethod] + public void DisallowsEncodingAsciiInsideGroupBeforeCallingNext1() + { + var bEncoder = EncodeUntilGroupWithAsciiInside(); + + Exception exception = Assert.ThrowsException(() => bEncoder.SetC("EURUSD")); + + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsEncodingAsciiInsideGroupBeforeCallingNext2() + { + var bEncoder = EncodeUntilGroupWithAsciiInside(); + + Exception exception = Assert.ThrowsException(() => bEncoder.SetC(0, (byte)'E')); + + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsEncodingAsciiInsideGroupBeforeCallingNext3() + { + var bEncoder = EncodeUntilGroupWithAsciiInside(); + + Exception exception = + Assert.ThrowsException(() => bEncoder.CAsSpan()[0] = (byte)'E'); + + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsEncodingAsciiInsideGroupBeforeCallingNext4() + { + var bEncoder = EncodeUntilGroupWithAsciiInside(); + + Exception exception = Assert.ThrowsException(() => + { + bEncoder.SetC(Encoding.ASCII.GetBytes("EURUSD"), 0); + }); + + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsEncodingAsciiInsideGroupBeforeCallingNext5() + { + var bEncoder = EncodeUntilGroupWithAsciiInside(); + + Exception exception = Assert.ThrowsException(() => + bEncoder.SetC(Encoding.ASCII.GetBytes("EURUSD"), 0)); + + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsEncodingAsciiInsideGroupBeforeCallingNext6() + { + var bEncoder = EncodeUntilGroupWithAsciiInside(); + + Exception exception = Assert.ThrowsException(() => + bEncoder.SetC(Encoding.ASCII.GetBytes("EURUSD"))); + + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + private AsciiInsideGroup.BGroup EncodeUntilGroupWithAsciiInside() + { + var encoder = new AsciiInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.SetA("GBPUSD"); + return encoder.BCount(1); + } + + [TestMethod] + public void DisallowsDecodingAsciiInsideGroupBeforeCallingNext1() + { + var b = DecodeUntilGroupWithAsciiInside(); + + Exception exception = Assert.ThrowsException(() => b.GetC(0)); + + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsDecodingAsciiInsideGroupBeforeCallingNext2() + { + var b = DecodeUntilGroupWithAsciiInside(); + + Exception exception = Assert.ThrowsException(() => b.GetC()); + + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsDecodingAsciiInsideGroupBeforeCallingNext3() + { + var b = DecodeUntilGroupWithAsciiInside(); + + Exception exception = Assert.ThrowsException(() => b.C[0]); + + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsDecodingAsciiInsideGroupBeforeCallingNext4() + { + var b = DecodeUntilGroupWithAsciiInside(); + + Exception exception = Assert.ThrowsException(() => b.GetC(new byte[16], 0)); + + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + [TestMethod] + public void DisallowsDecodingAsciiInsideGroupBeforeCallingNext5() + { + var b = DecodeUntilGroupWithAsciiInside(); + + Exception exception = Assert.ThrowsException(() => b.GetC(new byte[16])); + + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_B_N")); + } + + private AsciiInsideGroup.BGroup DecodeUntilGroupWithAsciiInside() + { + var encoder = new AsciiInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.SetA("GBPUSD"); + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.SetC("EURUSD"); + encoder.CheckEncodingIsComplete(); + + var decoder = new AsciiInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual("GBPUSD", decoder.GetA()); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + return bDecoder; + } + + [TestMethod] + public void AllowsReEncodingTopLevelAsciiViaReWrap() + { + var encoder = new AsciiInsideGroup(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.SetA("GBPUSD"); + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.SetC("EURUSD"); + + encoder.SetA("CADUSD"); + encoder.CheckEncodingIsComplete(); + + var decoder = new AsciiInsideGroup(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual("CADUSD", decoder.GetA()); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + bDecoder.Next(); + Assert.AreEqual("EURUSD", bDecoder.GetC()); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeAddedAsciiFieldBeforeGroup1() + { + var encoder = new AddAsciiBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.SetD("EURUSD"); + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddAsciiBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual("EURUSD", decoder.GetD()); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeAddedAsciiFieldBeforeGroup2() + { + var encoder = new AddAsciiBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.SetD("EURUSD"); + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddAsciiBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual((byte)'E', decoder.GetD(0)); + Assert.AreEqual((byte)'U', decoder.GetD(1)); + Assert.AreEqual((byte)'R', decoder.GetD(2)); + Assert.AreEqual((byte)'U', decoder.GetD(3)); + Assert.AreEqual((byte)'S', decoder.GetD(4)); + Assert.AreEqual((byte)'D', decoder.GetD(5)); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeAddedAsciiFieldBeforeGroup3() + { + var encoder = new AddAsciiBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.SetD("EURUSD"); + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddAsciiBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual((byte)'E', decoder.D[0]); + Assert.AreEqual((byte)'U', decoder.D[1]); + Assert.AreEqual((byte)'R', decoder.D[2]); + Assert.AreEqual((byte)'U', decoder.D[3]); + Assert.AreEqual((byte)'S', decoder.D[4]); + Assert.AreEqual((byte)'D', decoder.D[5]); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingAsciiFieldBeforeGroupAsNullValue1() + { + var encoder = new AddAsciiBeforeGroupV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddAsciiBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual(AddAsciiBeforeGroupV1.DNullValue, decoder.GetD(0)); + Assert.AreEqual(AddAsciiBeforeGroupV1.DNullValue, decoder.GetD(1)); + Assert.AreEqual(AddAsciiBeforeGroupV1.DNullValue, decoder.GetD(2)); + Assert.AreEqual(AddAsciiBeforeGroupV1.DNullValue, decoder.GetD(3)); + Assert.AreEqual(AddAsciiBeforeGroupV1.DNullValue, decoder.GetD(4)); + Assert.AreEqual(AddAsciiBeforeGroupV1.DNullValue, decoder.GetD(5)); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingAsciiFieldBeforeGroupAsNullValue2() + { + var encoder = new AddAsciiBeforeGroupV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddAsciiBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual(0, decoder.D.Length); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingAsciiFieldBeforeGroupAsNullValue3() + { + var encoder = new AddAsciiBeforeGroupV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddAsciiBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual(0, decoder.GetD(new byte[16])); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToDecodeMissingAsciiFieldBeforeGroupAsNullValue4() + { + var encoder = new AddAsciiBeforeGroupV0(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion0(); + + var decoder = new AddAsciiBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + Assert.AreEqual(0, decoder.GetD(new byte[16], 0)); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsNewDecoderToSkipPresentButAddedAsciiFieldBeforeGroup() + { + var encoder = new AddAsciiBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.SetD("EURUSD"); + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.C = 2; + encoder.CheckEncodingIsComplete(); + + var decoder = new AddAsciiBeforeGroupV1(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsOldDecoderToSkipAddedAsciiFieldBeforeGroup() + { + var encoder = new AddAsciiBeforeGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.SetD("EURUSD"); + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.C = 2; + encoder.CheckEncodingIsComplete(); + + ModifyHeaderToLookLikeVersion1(); + + var decoder = new AddAsciiBeforeGroupV0(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + } + + [TestMethod] + public void AllowsEncodeAndDecodeOfMessagesWithNoABlock() + { + var encoder = new NoBlock(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.SetA("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new NoBlock(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual("abc", decoder.GetA()); + } + + [TestMethod] + public void AllowsEncodeAndDecodeOfGroupsWithNoBlock() + { + var encoder = new GroupWithNoBlock(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + var aEncoder = encoder.ACount(1); + aEncoder.Next(); + aEncoder.SetB("abc"); + encoder.CheckEncodingIsComplete(); + + var decoder = new GroupWithNoBlock(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + var aDecoder = decoder.A; + Assert.AreEqual(1, aDecoder.Count); + Assert.AreEqual("abc", aDecoder.Next().GetB()); + } + + [TestMethod] + public void DisallowsEncodingElementOfEmptyGroup1() + { + var encoder = new MultipleGroups(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(0); + var dEncoder = encoder.DCount(1); + dEncoder.Next(); + dEncoder.E = 43; + + var ex = Assert.ThrowsException(() => bEncoder.C = 44); + Assert.IsTrue(ex.Message.Contains("Cannot access field \"b.c\" in state: V0_D_1_BLOCK")); + } + + [TestMethod] + public void DisallowsEncodingElementOfEmptyGroup2() + { + var encoder = new NestedGroups(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.C = 43; + var dEncoder = bEncoder.DCount(0); + bEncoder.FCount(0); + encoder.HCount(0); + + var ex = Assert.ThrowsException(() => dEncoder.E = 44); + Assert.IsTrue(ex.Message.Contains("Cannot access field \"b.d.e\" in state: V0_H_DONE")); + } + + [TestMethod] + public void DisallowsEncodingElementOfEmptyGroup3() + { + var encoder = new NestedGroups(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.C = 43; + var dEncoder = bEncoder.DCount(0); + bEncoder.FCount(0); + encoder.HCount(0); + + var ex = Assert.ThrowsException(() => dEncoder.E = 44); + Assert.IsTrue(ex.Message.Contains("Cannot access field \"b.d.e\" in state: V0_H_DONE")); + } + + [TestMethod] + public void DisallowsEncodingElementOfEmptyGroup4() + { + var encoder = new NestedGroups(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(1); + bEncoder.Next(); + bEncoder.C = 43; + var dEncoder = bEncoder.DCount(0); + bEncoder.FCount(0); + + var ex = Assert.ThrowsException(() => dEncoder.E = 44); + Assert.IsTrue(ex.Message.Contains("Cannot access field \"b.d.e\" in state: V0_B_1_F_DONE")); + } + + + [TestMethod] + public void DisallowsEncodingElementOfEmptyGroup5() + { + var encoder = new AddPrimitiveInsideGroupV1(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(0); + var exception = Assert.ThrowsException(() => bEncoder.C = 43); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V1_B_DONE")); + } + + [TestMethod] + public void DisallowsEncodingElementOfEmptyGroup6() + { + var encoder = new GroupAndVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 42; + var bEncoder = encoder.BCount(0); + encoder.SetD("abc"); + var exception = Assert.ThrowsException(() => bEncoder.C = 43); + Assert.IsTrue(exception.Message.Contains("Cannot access field \"b.c\" in state: V0_D_DONE")); + } + + [TestMethod] + public void AllowsEncodingAndDecodingNestedGroupWithVarDataInSchemaDefinedOrder() + { + var encoder = new NestedGroupWithVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(3); + var bNext = bEncoder.Next(); + bNext.C = 2; + bNext.DCount(0); + bNext = bEncoder.Next(); + bNext.C = 3; + var dEncoder = bNext.DCount(1); + var dNext = dEncoder.Next(); + dNext.E = 4; + dEncoder.SetF("abc"); + bNext = bEncoder.Next(); + bNext.C = 5; + dEncoder = bNext.DCount(2); + dNext = dEncoder.Next(); + dNext.E = 6; + dEncoder.SetF("def"); + dNext = dEncoder.Next(); + dNext.E = 7; + dEncoder.SetF("ghi"); + encoder.CheckEncodingIsComplete(); + + var decoder = new NestedGroupWithVarLength(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(3, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + Assert.AreEqual(0, bDecoder.D.Count); + Assert.AreEqual(3, bDecoder.Next().C); + var dDecoder = bDecoder.D; + Assert.AreEqual(1, dDecoder.Count); + Assert.AreEqual(4, dDecoder.Next().E); + Assert.AreEqual("abc", dDecoder.GetF()); + Assert.AreEqual(5, bDecoder.Next().C); + Assert.AreEqual(dDecoder, bDecoder.D); + Assert.AreEqual(2, dDecoder.Count); + Assert.AreEqual(6, dDecoder.Next().E); + Assert.AreEqual("def", dDecoder.GetF()); + Assert.AreEqual(7, dDecoder.Next().E); + Assert.AreEqual("ghi", dDecoder.GetF()); + } + + [DataTestMethod] + [DataRow(1, "V0_B_N_D_1_BLOCK")] + [DataRow(2, "V0_B_N_D_N_BLOCK")] + public void DisallowsMissedEncodingOfVarLengthFieldInNestedGroupToNextOuterElement(int dCount, + string expectedState) + { + var encoder = new NestedGroupWithVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(2); + var bNext = bEncoder.Next(); + bNext.C = 3; + var dEncoder = bNext.DCount(dCount); + dEncoder.Next().E = 4; + var exception = Assert.ThrowsException(bEncoder.Next); + Assert.IsTrue(exception.Message.Contains("Cannot access next element in repeating group \"b\" in state: " + + expectedState)); + Assert.IsTrue( + exception.Message.Contains( + "Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + } + + [TestMethod] + public void DisallowsMissedDecodingOfVarLengthFieldInNestedGroupToNextInnerElement1() + { + var encoder = new NestedGroupWithVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(1); + var bNext = bEncoder.Next(); + bNext.C = 2; + var dEncoder = bNext.DCount(2); + var dNext = dEncoder.Next(); + dNext.E = 3; + dEncoder.SetF("abc"); + dNext = dEncoder.Next(); + dNext.E = 4; + dEncoder.SetF("def"); + encoder.CheckEncodingIsComplete(); + + var decoder = new NestedGroupWithVarLength(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(1, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + var dDecoder = bDecoder.D; + Assert.AreEqual(2, dDecoder.Count); + Assert.AreEqual(3, dDecoder.Next().E); + var exception = Assert.ThrowsException(dDecoder.Next); + Assert.IsTrue( + exception.Message.Contains( + "Cannot access next element in repeating group \"b.d\" in state: V0_B_1_D_N_BLOCK.")); + Assert.IsTrue( + exception.Message.Contains( + "Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + } + + [TestMethod] + public void DisallowsMissedDecodingOfVarLengthFieldInNestedGroupToNextInnerElement2() + { + var encoder = new NestedGroupWithVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(2); + var bNext = bEncoder.Next(); + bNext.C = 2; + var dEncoder = bNext.DCount(2); + var dNext = dEncoder.Next(); + dNext.E = 3; + dEncoder.SetF("abc"); + dNext = dEncoder.Next(); + dNext.E = 4; + dEncoder.SetF("def"); + bEncoder.Next().C = 5; + bEncoder.DCount(0); + encoder.CheckEncodingIsComplete(); + + var decoder = new NestedGroupWithVarLength(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(2, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + var dDecoder = bDecoder.D; + Assert.AreEqual(2, dDecoder.Count); + Assert.AreEqual(3, dDecoder.Next().E); + var exception = Assert.ThrowsException(dDecoder.Next); + Assert.IsTrue( + exception.Message.Contains( + "Cannot access next element in repeating group \"b.d\" in state: V0_B_N_D_N_BLOCK.")); + Assert.IsTrue( + exception.Message.Contains( + "Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + } + + [TestMethod] + public void DisallowsMissedDecodingOfVarLengthFieldInNestedGroupToNextOuterElement1() + { + var encoder = new NestedGroupWithVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(2); + var bNext = bEncoder.Next(); + bNext.C = 2; + var dEncoder = bNext.DCount(2); + var dNext = dEncoder.Next(); + dNext.E = 3; + dEncoder.SetF("abc"); + dNext = dEncoder.Next(); + dNext.E = 4; + dEncoder.SetF("def"); + bEncoder.Next().C = 5; + bEncoder.DCount(0); + encoder.CheckEncodingIsComplete(); + + var decoder = new NestedGroupWithVarLength(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(2, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + var dDecoder = bDecoder.D; + Assert.AreEqual(2, dDecoder.Count); + Assert.AreEqual(3, dDecoder.Next().E); + var exception = Assert.ThrowsException(bDecoder.Next); + Assert.IsTrue( + exception.Message.Contains( + "Cannot access next element in repeating group \"b\" in state: V0_B_N_D_N_BLOCK.")); + Assert.IsTrue( + exception.Message.Contains( + "Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + } + + [TestMethod] + public void DisallowsMissedDecodingOfVarLengthFieldInNestedGroupToNextOuterElement2() + { + var encoder = new NestedGroupWithVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(2); + var bNext = bEncoder.Next(); + bNext.C = 2; + var dEncoder = bNext.DCount(1); + var dNext = dEncoder.Next(); + dNext.E = 3; + dEncoder.SetF("abc"); + bEncoder.Next().C = 5; + bEncoder.DCount(0); + encoder.CheckEncodingIsComplete(); + + var decoder = new NestedGroupWithVarLength(); + decoder.WrapForDecodeAndApplyHeader(_buffer, Offset, _messageHeader); + Assert.AreEqual(1, decoder.A); + var bDecoder = decoder.B; + Assert.AreEqual(2, bDecoder.Count); + Assert.AreEqual(2, bDecoder.Next().C); + var dDecoder = bDecoder.D; + Assert.AreEqual(1, dDecoder.Count); + Assert.AreEqual(3, dDecoder.Next().E); + var exception = Assert.ThrowsException(() => bDecoder.Next()); + Assert.IsTrue( + exception.Message.Contains( + "Cannot access next element in repeating group \"b\" in state: V0_B_N_D_1_BLOCK.")); + Assert.IsTrue( + exception.Message.Contains( + "Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + } + + [TestMethod] + public void DisallowsIncompleteMessagesDueToMissingVarLengthField1() + { + var encoder = new MultipleVarLength(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.SetB("abc"); + var exception = Assert.ThrowsException(() => encoder.CheckEncodingIsComplete()); + Assert.IsTrue( + exception.Message.Contains( + "Not fully encoded, current state: V0_B_DONE, allowed transitions: \"cLength()\", \"c(?)\"")); + } + + [TestMethod] + public void DisallowsIncompleteMessagesDueToMissingVarLengthField2() + { + var encoder = new NoBlock(); + encoder.WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + var exception = Assert.ThrowsException(() => encoder.CheckEncodingIsComplete()); + Assert.IsTrue( + exception.Message.Contains( + "Not fully encoded, current state: V0_BLOCK, allowed transitions: \"aLength()\", \"a(?)\"")); + } + + [TestMethod] + public void DisallowsIncompleteMessagesDueToMissingTopLevelGroup1() + { + var encoder = new MultipleGroups() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + encoder.BCount(0); + var exception = Assert.ThrowsException(encoder.CheckEncodingIsComplete); + StringAssert.Contains(exception.Message, + "Not fully encoded, current state: V0_B_DONE, allowed transitions: " + + "\"b.resetCountToIndex()\", \"dCount(0)\", \"dCount(>0)\""); + } + + [TestMethod] + public void DisallowsIncompleteMessagesDueToMissingTopLevelGroup2() + { + var encoder = new MultipleGroups() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(1).Next(); + bEncoder.C = 2; + var exception = Assert.ThrowsException(encoder.CheckEncodingIsComplete); + StringAssert.Contains(exception.Message, + "Not fully encoded, current state: V0_B_1_BLOCK, allowed transitions:" + + " \"b.c(?)\", \"b.resetCountToIndex()\", \"dCount(0)\", \"dCount(>0)\""); + } + + [TestMethod] + public void DisallowsIncompleteMessagesDueToMissingTopLevelGroup3() + { + var encoder = new MultipleGroups() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var exception = Assert.ThrowsException(encoder.CheckEncodingIsComplete); + StringAssert.Contains(exception.Message, + "Not fully encoded, current state: V0_BLOCK, allowed transitions: \"a(?)\", \"bCount(0)\", \"bCount(>0)\""); + } + + [DataTestMethod] + [DataRow(1, "V0_B_1_BLOCK")] + [DataRow(2, "V0_B_N_BLOCK")] + public void DisallowsIncompleteMessagesDueToMissingNestedGroup1(int bCount, string expectedState) + { + var encoder = new NestedGroupWithVarLength() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(bCount).Next(); + bEncoder.C = 2; + var exception = Assert.ThrowsException(encoder.CheckEncodingIsComplete); + StringAssert.Contains(exception.Message, $"Not fully encoded, current state: {expectedState}"); + } + + [DataTestMethod] + [DataRow(1, 1, "V0_B_1_D_N")] + [DataRow(1, 2, "V0_B_1_D_N")] + [DataRow(2, 0, "V0_B_N_D_DONE")] + [DataRow(2, 1, "V0_B_N_D_N")] + [DataRow(2, 2, "V0_B_N_D_N")] + public void DisallowsIncompleteMessagesDueToMissingNestedGroup2( + int bCount, + int dCount, + string expectedState) + { + var encoder = new NestedGroupWithVarLength() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(bCount).Next(); + bEncoder.C = 2; + bEncoder.DCount(dCount); + var exception = Assert.ThrowsException(encoder.CheckEncodingIsComplete); + StringAssert.Contains(exception.Message, $"Not fully encoded, current state: {expectedState}"); + } + + [DataTestMethod] + [DataRow(1, 1, "V0_B_1_D_1_BLOCK")] + [DataRow(1, 2, "V0_B_1_D_N_BLOCK")] + [DataRow(2, 1, "V0_B_N_D_1_BLOCK")] + [DataRow(2, 2, "V0_B_N_D_N_BLOCK")] + public void DisallowsIncompleteMessagesDueToMissingVarDataInNestedGroup( + int bCount, + int dCount, + string expectedState) + { + var encoder = new NestedGroupWithVarLength() + .WrapForEncodeAndApplyHeader(_buffer, Offset, _messageHeader); + encoder.A = 1; + var bEncoder = encoder.BCount(bCount).Next(); + bEncoder.C = 2; + bEncoder.DCount(dCount).Next().E = 10; + var exception = Assert.ThrowsException(encoder.CheckEncodingIsComplete); + StringAssert.Contains(exception.Message, $"Not fully encoded, current state: {expectedState}"); + } + + private void ModifyHeaderToLookLikeVersion0() + { + var messageHeaderDecoder = new MessageHeader(); + messageHeaderDecoder.Wrap(_buffer, Offset, MessageHeader.SbeSchemaVersion); + int v1TemplateId = messageHeaderDecoder.TemplateId + 1_000; + var messageHeaderEncoder = new MessageHeader(); + messageHeaderEncoder.Wrap(_buffer, Offset, MessageHeader.SbeSchemaVersion); + messageHeaderEncoder.TemplateId = (ushort)v1TemplateId; + messageHeaderEncoder.Version = 0; + } + + private void ModifyHeaderToLookLikeVersion1() + { + var messageHeaderDecoder = new MessageHeader(); + messageHeaderDecoder.Wrap(_buffer, Offset, MessageHeader.SbeSchemaVersion); + Debug.Assert(messageHeaderDecoder.Version == 1); + int v0TemplateId = messageHeaderDecoder.TemplateId - 1_000; + var messageHeaderEncoder = new MessageHeader(); + messageHeaderEncoder.Wrap(_buffer, Offset, MessageHeader.SbeSchemaVersion); + messageHeaderEncoder.TemplateId = (ushort)v0TemplateId; + } + } +} \ No newline at end of file diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/SbeTool.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/SbeTool.java index 1134e90c0e..7c5ec14a9f 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/SbeTool.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/SbeTool.java @@ -204,6 +204,24 @@ public class SbeTool */ public static final String SCHEMA_TRANSFORM_VERSION = "sbe.schema.transform.version"; + /** + * Whether to generate field precedence checks. For example, whether to check that repeating groups are encoded + * in schema order. + */ + public static final String GENERATE_PRECEDENCE_CHECKS = "sbe.generate.precedence.checks"; + + /** + * The name of the symbol or macro that enables access order checks when building + * generated C# or C++ code. + */ + public static final String PRECEDENCE_CHECKS_FLAG_NAME = "sbe.precedence.checks.flag.name"; + + /** + * The name of the system property that enables access order checks at runtime + * in generated Java code. + */ + public static final String JAVA_PRECEDENCE_CHECKS_PROPERTY_NAME = "sbe.java.precedence.checks.property.name"; + /** * Main entry point for the SBE Tool. * diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/TargetCodeGeneratorLoader.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/TargetCodeGeneratorLoader.java index 5d81eb691b..6552a72c00 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/TargetCodeGeneratorLoader.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/TargetCodeGeneratorLoader.java @@ -15,14 +15,15 @@ */ package uk.co.real_logic.sbe.generation; -import uk.co.real_logic.sbe.generation.java.JavaOutputManager; import uk.co.real_logic.sbe.generation.c.CGenerator; import uk.co.real_logic.sbe.generation.c.COutputManager; +import uk.co.real_logic.sbe.generation.common.PrecedenceChecks; import uk.co.real_logic.sbe.generation.cpp.CppGenerator; import uk.co.real_logic.sbe.generation.cpp.NamespaceOutputManager; import uk.co.real_logic.sbe.generation.golang.GolangGenerator; import uk.co.real_logic.sbe.generation.golang.GolangOutputManager; import uk.co.real_logic.sbe.generation.java.JavaGenerator; +import uk.co.real_logic.sbe.generation.java.JavaOutputManager; import uk.co.real_logic.sbe.generation.rust.RustGenerator; import uk.co.real_logic.sbe.generation.rust.RustOutputManager; import uk.co.real_logic.sbe.ir.Ir; @@ -53,6 +54,7 @@ public CodeGenerator newInstance(final Ir ir, final String outputDir) "true".equals(System.getProperty(JAVA_GENERATE_INTERFACES)), "true".equals(System.getProperty(DECODE_UNKNOWN_ENUM_VALUES)), "true".equals(System.getProperty(TYPES_PACKAGE_OVERRIDE)), + precedenceChecks(), new JavaOutputManager(outputDir, ir.applicableNamespace())); } }, @@ -84,6 +86,7 @@ public CodeGenerator newInstance(final Ir ir, final String outputDir) return new CppGenerator( ir, "true".equals(System.getProperty(DECODE_UNKNOWN_ENUM_VALUES)), + precedenceChecks(), new NamespaceOutputManager(outputDir, ir.applicableNamespace())); } }, @@ -118,6 +121,36 @@ public CodeGenerator newInstance(final Ir ir, final String outputDir) } }; + /** + * Returns the precedence checks to run, configured from system properties. + * + * @return the precedence checks to run, configured from system properties. + */ + public static PrecedenceChecks precedenceChecks() + { + final PrecedenceChecks.Context context = new PrecedenceChecks.Context(); + + final String shouldGeneratePrecedenceChecks = System.getProperty(GENERATE_PRECEDENCE_CHECKS); + if (shouldGeneratePrecedenceChecks != null) + { + context.shouldGeneratePrecedenceChecks(Boolean.parseBoolean(shouldGeneratePrecedenceChecks)); + } + + final String precedenceChecksFlagName = System.getProperty(PRECEDENCE_CHECKS_FLAG_NAME); + if (precedenceChecksFlagName != null) + { + context.precedenceChecksFlagName(precedenceChecksFlagName); + } + + final String precedenceChecksPropName = System.getProperty(JAVA_PRECEDENCE_CHECKS_PROPERTY_NAME); + if (precedenceChecksPropName != null) + { + context.precedenceChecksPropName(precedenceChecksPropName); + } + + return PrecedenceChecks.newInstance(context); + } + /** * Do a case-insensitive lookup of a target language for code generation. * diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/common/FieldPrecedenceModel.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/common/FieldPrecedenceModel.java new file mode 100644 index 0000000000..0e79f88568 --- /dev/null +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/common/FieldPrecedenceModel.java @@ -0,0 +1,1190 @@ +/* + * Copyright 2013-2023 Real Logic Limited. + * + * 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 + * + * https://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. + */ +package uk.co.real_logic.sbe.generation.common; + +import uk.co.real_logic.sbe.generation.Generators; +import uk.co.real_logic.sbe.ir.Signal; +import uk.co.real_logic.sbe.ir.Token; +import org.agrona.collections.Int2ObjectHashMap; +import org.agrona.collections.IntHashSet; +import org.agrona.collections.IntObjConsumer; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.IntStream; + +import static uk.co.real_logic.sbe.ir.GenerationUtil.collectFields; +import static uk.co.real_logic.sbe.ir.GenerationUtil.collectGroups; +import static uk.co.real_logic.sbe.ir.GenerationUtil.collectVarData; + +/** + * A state machine that models whether codec interactions are safe. + */ +@SuppressWarnings("CodeBlock2Expr") +// Lambdas without braces tend to conflict with the indentation Checkstyle expects. +// Therefore, we allow lambdas with code blocks even when a lambda expression is possible. +public final class FieldPrecedenceModel +{ + private final Map groupPathsByField = new HashMap<>(); + private final Set topLevelBlockFields = new HashSet<>(); + private final CodecInteraction.CodecInteractionFactory interactionFactory = + new CodecInteraction.CodecInteractionFactory(groupPathsByField, topLevelBlockFields); + private final Map> transitionsByInteraction = new LinkedHashMap<>(); + private final Map> transitionsByState = new HashMap<>(); + private final Int2ObjectHashMap versionWrappedStates = new Int2ObjectHashMap<>(); + private final State notWrappedState = allocateState("NOT_WRAPPED"); + private final String generatedRepresentationClassName; + private State encoderWrappedState; + private Set terminalEncoderStates; + + private FieldPrecedenceModel(final String generatedRepresentationClassName) + { + this.generatedRepresentationClassName = generatedRepresentationClassName; + } + + /** + * Builds a state machine that models whether codec interactions are safe. + * + * @param stateClassName the qualified name of the class that models the state machine in generated code + * @param msgToken the message token + * @param fields the fields in the message + * @param groups the groups in the message + * @param varData the varData in the message + * @param versionsSelector a function that selects the versions to model in the state machine + * @return the access order model + */ + public static FieldPrecedenceModel newInstance( + final String stateClassName, + final Token msgToken, + final List fields, + final List groups, + final List varData, + final Function versionsSelector) + { + final FieldPrecedenceModel model = new FieldPrecedenceModel(stateClassName); + model.findTransitions(msgToken, fields, groups, varData, versionsSelector); + return model; + } + + /** + * The initial state before a codec is wrapped. + * @return the initial state before a codec is wrapped. + */ + public State notWrappedState() + { + return notWrappedState; + } + + /** + * The state after a codec is wrapped in the latest version. + * @return the state after a codec is wrapped in the latest version. + */ + public State latestVersionWrappedState() + { + return encoderWrappedState; + } + + /** + * Iterates over the states after a codec is wrapped over a particular version of data. + * @param consumer the consumer of the states. + */ + public void forEachWrappedStateByVersion(final IntObjConsumer consumer) + { + final Int2ObjectHashMap.EntryIterator iterator = versionWrappedStates.entrySet().iterator(); + while (iterator.hasNext()) + { + iterator.next(); + consumer.accept(iterator.getIntKey(), iterator.getValue()); + } + } + + /** + * Returns the number of schema versions. + * @return the number of schema versions. + */ + public int versionCount() + { + return versionWrappedStates.size(); + } + + /** + * Iterates over the states in which a codec is fully encoded. + * @param consumer the consumer of the states. + */ + public void forEachTerminalEncoderState(final Consumer consumer) + { + terminalEncoderStates.forEach(consumer); + } + + /** + * Iterates over the codec states in the order of their state numbers. + * @param consumer the consumer of the states. + */ + public void forEachStateOrderedByStateNumber(final Consumer consumer) + { + transitionsByState.keySet().stream() + .sorted(Comparator.comparingInt(s -> s.number)) + .forEach(consumer); + } + + /** + * Returns the number of states in the state machine. + * @return the number of states in the state machine. + */ + public int stateCount() + { + return transitionsByState.size(); + } + + /** + * Returns a hash-consing factory for codec interactions. + * These interactions are the transitions in the state machine. + * @see CodecInteraction + * @return a hash-consing factory for codec interactions. + */ + public CodecInteraction.CodecInteractionFactory interactionFactory() + { + return interactionFactory; + } + + /** + * Returns the name of the class that models the state machine in generated code. + * @return the name of the class that models the state machine in generated code. + */ + public String generatedRepresentationClassName() + { + return generatedRepresentationClassName; + } + + /** + * Iterates over the possible state machine transitions due to the supplied codec interaction. + * @param interaction a codec interaction. + * @param consumer the consumer of the transitions. + */ + public void forEachTransition( + final CodecInteraction interaction, + final Consumer consumer) + { + final List transitionsForContext = transitionsByInteraction.get(interaction); + if (null != transitionsForContext) + { + transitionsForContext.forEach(consumer); + } + } + + /** + * Finds the possible transitions from a given state. + * @param state the state to find transitions from. + * @param consumer the consumer of the transitions. + */ + public void forEachTransitionFrom(final State state, final Consumer consumer) + { + final List transitionGroups = transitionsByState.get(state); + if (null != transitionGroups) + { + transitionGroups.forEach(consumer); + } + } + + /** + * Encodes the state machine as a graphviz dot diagram. + * @param sb the string builder to append to. + * @param indent the indentation to use. + */ + @SuppressWarnings("SameParameterValue") + public void generateGraph(final StringBuilder sb, final String indent) + { + sb.append(indent).append("digraph G {\n"); + transitionsByInteraction.values().forEach(transitionsForContext -> + { + transitionsForContext.forEach(transition -> + { + transition.forEachStartState(startState -> + { + sb.append(indent).append(" ") + .append(startState.name) + .append(" -> ") + .append(transition.endState().name) + .append(" [label=\" ").append(transition.interaction.exampleCode()); + + if (!transition.interaction.exampleConditions().isEmpty()) + { + sb.append("\\n").append(" where ").append(transition.interaction.exampleConditions()); + } + + sb.append(" \"];\n"); + }); + }); + }); + sb.append(indent).append("}\n"); + } + + private void findTransitions( + final Token msgToken, + final List fields, + final List groups, + final List varData, + final Function versionsSelector) + { + final IntHashSet versions = new IntHashSet(); + versions.add(msgToken.version()); + walkSchemaLevel(new VersionCollector(versions), fields, groups, varData); + walkSchemaLevel(new PathCollector(topLevelBlockFields, groupPathsByField), fields, groups, varData); + + final IntStream selectedVersions = versionsSelector.apply(versions.stream().mapToInt(i -> i)); + selectedVersions.sorted().forEach(version -> + { + final State versionWrappedState = allocateState("V" + version + "_BLOCK"); + + versionWrappedStates.put(version, versionWrappedState); + + final CodecInteraction wrapInteraction = interactionFactory.wrap(version); + + allocateTransitions( + wrapInteraction, + Collections.singletonList(notWrappedState), + versionWrappedState + ); + + final TransitionCollector transitionCollector = new TransitionCollector( + "V" + version + "_", + Collections.singleton(versionWrappedState), + versionWrappedState, + token -> token.version() <= version + ); + + walkSchemaLevel(transitionCollector, fields, groups, varData); + + // Last writer (highest version) wins when there are multiple versions + encoderWrappedState = versionWrappedState; + terminalEncoderStates = transitionCollector.exitStates(); + }); + } + + private State allocateState(final String name) + { + final State state = new State(transitionsByState.size(), name); + transitionsByState.put(state, new ArrayList<>()); + return state; + } + + private void allocateTransitions( + final CodecInteraction interaction, + final Collection from, + final State to) + { + final TransitionGroup transitionGroup = new TransitionGroup(interaction, from, to); + final List transitionsForInteraction = + transitionsByInteraction.computeIfAbsent(interaction, ignored -> new ArrayList<>()); + + final boolean duplicateEndState = + transitionsForInteraction.stream().anyMatch(t -> t.to.number == transitionGroup.to.number); + + if (duplicateEndState) + { + throw new IllegalStateException("Duplicate end state: " + transitionGroup.to.name); + } + + final Optional conflictingTransition = transitionsForInteraction.stream() + .filter(t -> t.from.stream().anyMatch(transitionGroup.from::contains)) + .findAny(); + + if (conflictingTransition.isPresent()) + { + throw new IllegalStateException( + "Conflicting transition: " + transitionGroup + " conflicts with " + conflictingTransition.get()); + } + + transitionsForInteraction.add(transitionGroup); + + from.forEach(fromState -> transitionsByState.get(fromState).add(transitionGroup)); + } + + private static void walkSchemaLevel( + final SchemaConsumer consumer, + final List fields, + final List groups, + final List varData) + { + Generators.forEachField(fields, (token, ignored) -> consumer.onBlockField(token)); + + for (int i = 0; i < groups.size(); i++) + { + final Token token = groups.get(i); + if (token.signal() != Signal.BEGIN_GROUP) + { + throw new IllegalStateException("tokens must begin with BEGIN_GROUP: token=" + token); + } + + ++i; + final int groupHeaderTokenCount = groups.get(i).componentTokenCount(); + i += groupHeaderTokenCount; + + final ArrayList groupFields = new ArrayList<>(); + i = collectFields(groups, i, groupFields); + final ArrayList groupGroups = new ArrayList<>(); + i = collectGroups(groups, i, groupGroups); + final ArrayList groupVarData = new ArrayList<>(); + i = collectVarData(groups, i, groupVarData); + + consumer.onEnterRepeatingGroup(token, groupFields, groupGroups, groupVarData); + } + + for (int i = 0; i < varData.size(); ) + { + final Token token = varData.get(i); + if (token.signal() != Signal.BEGIN_VAR_DATA) + { + throw new IllegalStateException("tokens must begin with BEGIN_VAR_DATA: token=" + token); + } + i += token.componentTokenCount(); + + consumer.onVarData(token); + } + } + + private interface SchemaConsumer + { + void onBlockField(Token token); + + void onEnterRepeatingGroup( + Token token, + List groupFields, + List groupGroups, + List groupVarData); + + void onVarData(Token token); + } + + private static final class PathCollector implements SchemaConsumer + { + private final ArrayDeque groupPath = new ArrayDeque<>(); + private final Set topLevelBlockFields; + private final Map groupPathsByField; + + private PathCollector( + final Set topLevelBlockFields, + final Map groupPathsByField) + { + this.topLevelBlockFields = topLevelBlockFields; + this.groupPathsByField = groupPathsByField; + } + + @Override + public void onBlockField(final Token token) + { + if (groupPath.isEmpty()) + { + topLevelBlockFields.add(token); + } + + groupPathsByField.put(token, currentGroupPath()); + } + + @Override + public void onEnterRepeatingGroup( + final Token token, + final List groupFields, + final List groupGroups, + final List groupVarData) + { + groupPathsByField.put(token, currentGroupPath()); + groupPath.addLast(token); + walkSchemaLevel(this, groupFields, groupGroups, groupVarData); + groupPath.removeLast(); + } + + @Override + public void onVarData(final Token token) + { + groupPathsByField.put(token, currentGroupPath()); + } + + private String currentGroupPath() + { + final StringBuilder sb = new StringBuilder(); + groupPath.forEach(token -> + { + sb.append(token.name()).append('.'); + }); + return sb.toString(); + } + } + + @SuppressWarnings("ClassCanBeRecord") + private static final class VersionCollector implements SchemaConsumer + { + private final IntHashSet versions; + + VersionCollector(final IntHashSet versions) + { + this.versions = versions; + } + + @Override + public void onBlockField(final Token token) + { + versions.add(token.version()); + } + + @Override + public void onEnterRepeatingGroup( + final Token token, + final List groupFields, + final List groupGroups, + final List groupVarData) + { + versions.add(token.version()); + walkSchemaLevel(this, groupFields, groupGroups, groupVarData); + } + + @Override + public void onVarData(final Token token) + { + versions.add(token.version()); + } + } + + private final class TransitionCollector implements SchemaConsumer + { + private final String statePrefix; + private final HashSet currentStates; + private final State blockState; + private final Predicate filter; + + private TransitionCollector( + final String statePrefix, + final Set fromStates, + final State blockState, + final Predicate filter) + { + this.statePrefix = statePrefix; + this.currentStates = new HashSet<>(fromStates); + this.blockState = blockState; + this.filter = filter; + + currentStates.add(blockState); + } + + @Override + public void onBlockField(final Token token) + { + if (filter.test(token)) + { + final CodecInteraction codecInteraction = interactionFactory.accessField(token); + allocateTransitions(codecInteraction, currentStates, blockState); + } + } + + @Override + public void onEnterRepeatingGroup( + final Token token, + final List groupFields, + final List groupGroups, + final List groupVarData) + { + if (filter.test(token)) + { + final String groupName = token.name().toUpperCase(); + final String groupPrefix = statePrefix + groupName + "_"; + final State nRemainingGroup = allocateState(groupPrefix + "N"); + final State nRemainingGroupElement = allocateState(groupPrefix + "N_BLOCK"); + final State oneRemainingGroupElement = allocateState(groupPrefix + "1_BLOCK"); + final State doneGroup = allocateState(groupPrefix + "DONE"); + + final Set beginGroupStates = new HashSet<>(currentStates); + + // fooCount(0) + final CodecInteraction emptyGroupInteraction = interactionFactory.determineGroupIsEmpty(token); + allocateTransitions(emptyGroupInteraction, beginGroupStates, doneGroup); + + // fooCount(N) where N > 0 + final CodecInteraction nonEmptyGroupInteraction = interactionFactory.determineGroupHasElements(token); + allocateTransitions(nonEmptyGroupInteraction, beginGroupStates, nRemainingGroup); + + currentStates.clear(); + currentStates.add(nRemainingGroupElement); + final TransitionCollector nRemainingCollector = new TransitionCollector( + groupPrefix + "N_", + currentStates, + nRemainingGroupElement, + filter + ); + walkSchemaLevel(nRemainingCollector, groupFields, groupGroups, groupVarData); + + currentStates.clear(); + currentStates.add(nRemainingGroup); + currentStates.addAll(nRemainingCollector.exitStates()); + + // where more than one element remains in the group + final CodecInteraction nextGroupElementInteraction = interactionFactory.moveToNextElement(token); + allocateTransitions(nextGroupElementInteraction, currentStates, nRemainingGroupElement); + + currentStates.clear(); + currentStates.add(nRemainingGroup); + currentStates.addAll(nRemainingCollector.exitStates()); + + // where only one element remains in the group + final CodecInteraction lastGroupElementInteraction = interactionFactory.moveToLastElement(token); + allocateTransitions(lastGroupElementInteraction, currentStates, oneRemainingGroupElement); + + currentStates.clear(); + currentStates.add(oneRemainingGroupElement); + + final TransitionCollector oneRemainingCollector = new TransitionCollector( + groupPrefix + "1_", + currentStates, + oneRemainingGroupElement, + filter + ); + walkSchemaLevel(oneRemainingCollector, groupFields, groupGroups, groupVarData); + + final CodecInteraction resetCountToIndexInteraction = interactionFactory.resetCountToIndex(token); + currentStates.clear(); + currentStates.add(doneGroup); + currentStates.add(nRemainingGroup); + currentStates.addAll(nRemainingCollector.exitStates()); + currentStates.addAll(oneRemainingCollector.exitStates()); + allocateTransitions(resetCountToIndexInteraction, currentStates, doneGroup); + + currentStates.clear(); + currentStates.add(doneGroup); + currentStates.addAll(oneRemainingCollector.exitStates()); + } + } + + @Override + public void onVarData(final Token token) + { + if (filter.test(token)) + { + final CodecInteraction lengthAccessInteraction = interactionFactory.accessVarDataLength(token); + currentStates.forEach(state -> + { + allocateTransitions(lengthAccessInteraction, Collections.singleton(state), state); + }); + + final CodecInteraction codecInteraction = interactionFactory.accessField(token); + final State accessedState = allocateState(statePrefix + token.name().toUpperCase() + "_DONE"); + allocateTransitions(codecInteraction, currentStates, accessedState); + currentStates.clear(); + currentStates.add(accessedState); + } + } + + private Set exitStates() + { + return currentStates; + } + } + + /** + * A state in which a codec may reside. + */ + public static final class State + { + private final int number; + private final String name; + + private State(final int number, final String name) + { + this.number = number; + this.name = name; + } + + /** + * In the scope of an {@code FieldPrecedenceModel} instance, state numbers are contiguous + * and start at 0. This numbering scheme allows easy generation of lookup tables. + * @return the state number + */ + public int number() + { + return number; + } + + /** + * Returns the name of the state. + * @return the name of the state. + */ + public String name() + { + return name; + } + + @Override + public String toString() + { + return "State{" + + "number=" + number + + ", name='" + name + '\'' + + '}'; + } + } + + /** + * A group of transitions that share the same end state. + */ + public static final class TransitionGroup + { + private final CodecInteraction interaction; + private final Set from; + private final State to; + + private TransitionGroup( + final CodecInteraction interaction, + final Collection from, + final State to) + { + this.interaction = interaction; + this.from = new HashSet<>(from); + this.to = to; + } + + /** + * Iterate over the start states of the transitions in this group. + * @param consumer the consumer of the start states. + */ + public void forEachStartState(final Consumer consumer) + { + from.forEach(consumer); + } + + /** + * Returns the end state of the transitions in this group. + * @return the end state of the transitions in this group. + */ + public State endState() + { + return to; + } + + /** + * Returns {@code true} if the transitions in this group do not change state. + * @return {@code true} if the transitions in this group do not change state. + */ + public boolean alwaysEndsInStartState() + { + return from.size() == 1 && from.contains(to); + } + + /** + * Returns some example code for the codec interaction that the transitions in this group share. + * Useful for producing error messages. + * @return some example code for the codec interaction that the transitions in this group share. + */ + public String exampleCode() + { + return interaction.exampleCode(); + } + + @Override + public String toString() + { + return "Transition{" + + "interaction='" + exampleCode() + '\'' + + ", from=" + from + + ", to=" + to + + '}'; + } + } + + /** + * Represents an interaction against a codec, e.g., {@code encoder.wrap(...)} or {@code decoder.myVarData()}. + */ + public abstract static class CodecInteraction + { + /** + * Returns a name for the interaction qualified by any groups that it is nested within. + * @return a name for the interaction qualified by any groups that it is nested within. + */ + public abstract String groupQualifiedName(); + + abstract String exampleCode(); + + abstract String exampleConditions(); + + /** + * Returns {@code true} if this interaction is a top-level block field access; {@code false} otherwise. + * @return {@code true} if this interaction is a top-level block field access; {@code false} otherwise. + */ + public final boolean isTopLevelBlockFieldAccess() + { + if (this instanceof AccessField) + { + //noinspection PatternVariableCanBeUsed + final AccessField accessField = (AccessField)this; + return accessField.isTopLevelBlockField(); + } + + return false; + } + + /** + * When an encoder or decoder is wrapped around a buffer. + */ + private static final class Wrap extends CodecInteraction + { + private final int version; + + Wrap(final int version) + { + this.version = version; + } + + @Override + public String groupQualifiedName() + { + return "wrap"; + } + + @Override + public String exampleCode() + { + return "wrap(version=" + version + ")"; + } + + @Override + public String exampleConditions() + { + return ""; + } + } + + /** + * When a block or variable-length field is accessed. + */ + private static final class AccessField extends CodecInteraction + { + private final String groupPath; + private final Token token; + private final boolean isTopLevelBlockField; + + private AccessField(final String groupPath, final Token token, final boolean isTopLevelBlockField) + { + this.isTopLevelBlockField = isTopLevelBlockField; + assert groupPath != null; + assert token.signal() == Signal.BEGIN_FIELD || token.signal() == Signal.BEGIN_VAR_DATA; + this.groupPath = groupPath; + this.token = token; + } + + boolean isTopLevelBlockField() + { + return isTopLevelBlockField; + } + + @Override + public String groupQualifiedName() + { + return groupPath + token.name(); + } + + @Override + public String exampleCode() + { + return groupPath + token.name() + "(?)"; + } + + @Override + public String exampleConditions() + { + return ""; + } + } + + /** + * When a repeating group count is supplied as zero. + */ + private static final class DetermineGroupIsEmpty extends CodecInteraction + { + private final String groupPath; + private final Token token; + + private DetermineGroupIsEmpty(final String groupPath, final Token token) + { + assert groupPath != null; + assert token.signal() == Signal.BEGIN_GROUP; + this.groupPath = groupPath; + this.token = token; + } + + @Override + public String groupQualifiedName() + { + return groupPath + token.name(); + } + + @Override + String exampleCode() + { + return groupPath + token.name() + "Count(0)"; + } + + @Override + String exampleConditions() + { + return ""; + } + } + + /** + * When a repeating group count is supplied as greater than zero. + */ + private static final class DetermineGroupHasElements extends CodecInteraction + { + private final String groupPath; + private final Token token; + + private DetermineGroupHasElements(final String groupPath, final Token token) + { + assert groupPath != null; + assert token.signal() == Signal.BEGIN_GROUP; + this.groupPath = groupPath; + this.token = token; + } + + @Override + public String groupQualifiedName() + { + return groupPath + token.name(); + } + + @Override + String exampleCode() + { + return groupPath + token.name() + "Count(>0)"; + } + + @Override + String exampleConditions() + { + return ""; + } + } + + /** + * When the next element in a repeating group is accessed, + * and it is not the last element. + */ + private static final class MoveToNextElement extends CodecInteraction + { + private final String groupPath; + private final Token token; + + private MoveToNextElement(final String groupPath, final Token token) + { + assert groupPath != null; + assert token.signal() == Signal.BEGIN_GROUP; + this.groupPath = groupPath; + this.token = token; + } + + @Override + public String groupQualifiedName() + { + return groupPath + token.name(); + } + + @Override + String exampleCode() + { + return groupPath + token.name() + ".next()"; + } + + @Override + String exampleConditions() + { + return "count - newIndex > 1"; + } + } + + /** + * When the next element in a repeating group is accessed, + * and it is the last element. + */ + private static final class MoveToLastElement extends CodecInteraction + { + private final String groupPath; + private final Token token; + + private MoveToLastElement(final String groupPath, final Token token) + { + assert groupPath != null; + assert token.signal() == Signal.BEGIN_GROUP; + this.groupPath = groupPath; + this.token = token; + } + + @Override + public String groupQualifiedName() + { + return groupPath + token.name(); + } + + @Override + String exampleCode() + { + return groupPath + token.name() + ".next()"; + } + + @Override + String exampleConditions() + { + return "count - newIndex == 1"; + } + } + + /** + * When the number of elements in a repeating group is set to be its current extent. + */ + private static final class ResetCountToIndex extends CodecInteraction + { + private final String groupPath; + private final Token token; + + private ResetCountToIndex(final String groupPath, final Token token) + { + assert groupPath != null; + assert token.signal() == Signal.BEGIN_GROUP; + this.groupPath = groupPath; + this.token = token; + } + + @Override + public String groupQualifiedName() + { + return groupPath + token.name(); + } + + @Override + String exampleCode() + { + return groupPath + token.name() + ".resetCountToIndex()"; + } + + @Override + String exampleConditions() + { + return ""; + } + } + + /** + * When the length of a variable length field is accessed without adjusting the position. + */ + private static final class AccessVarDataLength extends CodecInteraction + { + private final String groupPath; + private final Token token; + + private AccessVarDataLength(final String groupPath, final Token token) + { + assert groupPath != null; + assert token.signal() == Signal.BEGIN_VAR_DATA; + this.groupPath = groupPath; + this.token = token; + } + + @Override + public String groupQualifiedName() + { + return groupPath + token.name(); + } + + @Override + String exampleCode() + { + return groupPath + token.name() + "Length()"; + } + + @Override + String exampleConditions() + { + return ""; + } + } + + + /** + * Factory for creating {@link CodecInteraction} instances. This factory + * is used to hash-cons the instances, so that they can be compared by + * reference. + */ + public static final class CodecInteractionFactory + { + private final Int2ObjectHashMap wrapInteractions = new Int2ObjectHashMap<>(); + private final Map accessFieldInteractions = new HashMap<>(); + private final Map determineGroupIsEmptyInteractions = new HashMap<>(); + private final Map determineGroupHasElementsInteractions = new HashMap<>(); + private final Map moveToNextElementInteractions = new HashMap<>(); + private final Map moveToLastElementInteractions = new HashMap<>(); + private final Map resetCountToIndexInteractions = new HashMap<>(); + private final Map accessVarDataLengthInteractions = new HashMap<>(); + private final Map groupPathsByField; + private final Set topLevelBlockFields; + + CodecInteractionFactory( + final Map groupPathsByField, + final Set topLevelBlockFields) + { + + this.groupPathsByField = groupPathsByField; + this.topLevelBlockFields = topLevelBlockFields; + } + + /** + * Find or create a {@link CodecInteraction} to represent wrapping a codec around + * the given version of data. + * @param version the version of data to wrap + * @return the {@link CodecInteraction} instance + */ + public CodecInteraction wrap(final int version) + { + return wrapInteractions.computeIfAbsent(version, Wrap::new); + } + + /** + * Find or create a {@link CodecInteraction} to represent accessing the field identified + * by the given token. + * + *

The supplied token must carry a {@link Signal#BEGIN_FIELD} + * or {@link Signal#BEGIN_VAR_DATA} signal. + * + * @param token the token identifying the field + * @return the {@link CodecInteraction} instance + */ + public CodecInteraction accessField(final Token token) + { + return accessFieldInteractions.computeIfAbsent(token, + t -> new AccessField(groupPathsByField.get(t), t, topLevelBlockFields.contains(t))); + } + + /** + * Find or create a {@link CodecInteraction} to represent determining a + * repeating group is empty. + * + *

For encoding, this will be when the group count is supplied, e.g., + * {@code encoder.myGroupCount(0)}. + * + *

For decoding, this will be when the group is read, e.g., + * {@code decoder.myGroup()}. + * + *

The supplied token must carry a {@link Signal#BEGIN_GROUP} signal. + * + * @param token the token identifying the group + * @return the {@link CodecInteraction} instance + */ + public CodecInteraction determineGroupIsEmpty(final Token token) + { + return determineGroupIsEmptyInteractions.computeIfAbsent(token, + t -> new DetermineGroupIsEmpty(groupPathsByField.get(t), t)); + } + + /** + * Find or create a {@link CodecInteraction} to represent determining a + * repeating group has elements. + * + *

For encoding, this will be when the group count is supplied, e.g., + * {@code encoder.myGroupCount(1)}. + * + *

For decoding, this will be when the group is read, e.g., + * {@code decoder.myGroup()}. + * + *

The supplied token must carry a {@link Signal#BEGIN_GROUP} signal. + * + * @param token the token identifying the group + * @return the {@link CodecInteraction} instance + */ + public CodecInteraction determineGroupHasElements(final Token token) + { + return determineGroupHasElementsInteractions.computeIfAbsent(token, + t -> new DetermineGroupHasElements(groupPathsByField.get(t), t)); + } + + /** + * Find or create a {@link CodecInteraction} to represent moving to the next + * element in a repeating group. + * + *

For encoders, decoders, and codecs, this will be when the next element + * is accessed, e.g., {@code myGroup.next()} when {@code myGroup.count - myGroup.index > 1}. + * + *

The supplied token must carry a {@link Signal#BEGIN_GROUP} signal. + * + * @param token the token identifying the group + * @return the {@link CodecInteraction} instance + */ + public CodecInteraction moveToNextElement(final Token token) + { + return moveToNextElementInteractions.computeIfAbsent(token, + t -> new MoveToNextElement(groupPathsByField.get(t), t)); + } + + /** + * Find or create a {@link CodecInteraction} to represent moving to the last + * element in a repeating group. + * + *

For encoders, decoders, and codecs, this will be when the last element + * is accessed, e.g., {@code myGroup.next()} when {@code myGroup.count - myGroup.index == 1}. + * + * @param token the token identifying the group + * @return the {@link CodecInteraction} instance + */ + public CodecInteraction moveToLastElement(final Token token) + { + return moveToLastElementInteractions.computeIfAbsent(token, + t -> new MoveToLastElement(groupPathsByField.get(t), t)); + } + + /** + * Find or create a {@link CodecInteraction} to represent resetting the count + * of a repeating group to the current index. + * + *

For encoders, decoders, and codecs, this will be when the {@code resetCountToIndex} + * method is called, e.g., {@code myGroup.resetCountToIndex()}. + * + *

The supplied token must carry a {@link Signal#BEGIN_GROUP} signal. + * + * @param token the token identifying the group + * @return the {@link CodecInteraction} instance + */ + public CodecInteraction resetCountToIndex(final Token token) + { + return resetCountToIndexInteractions.computeIfAbsent(token, + t -> new ResetCountToIndex(groupPathsByField.get(t), t)); + } + + /** + * Find or create a {@link CodecInteraction} to represent accessing the length + * of a variable-length data field without advancing the codec position. + * + *

For decoders and codecs, this will be when the length is accessed, e.g., + * {@code decoder.myVarDataLength()}. + * + *

The supplied token must carry a {@link Signal#BEGIN_VAR_DATA} signal. + * + * @param token the token identifying the field + * @return the {@link CodecInteraction} instance + */ + public CodecInteraction accessVarDataLength(final Token token) + { + return accessVarDataLengthInteractions.computeIfAbsent(token, + t -> new AccessVarDataLength(groupPathsByField.get(t), t)); + } + } + } +} diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/common/PrecedenceChecks.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/common/PrecedenceChecks.java new file mode 100644 index 0000000000..9825a3e6b7 --- /dev/null +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/common/PrecedenceChecks.java @@ -0,0 +1,253 @@ +/* + * Copyright 2013-2023 Real Logic Limited. + * + * 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 + * + * https://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. + */ +package uk.co.real_logic.sbe.generation.common; + +import uk.co.real_logic.sbe.ir.Token; + +import java.util.ArrayList; +import java.util.List; +import java.util.OptionalInt; +import java.util.function.Function; +import java.util.stream.IntStream; + +import static java.util.Objects.requireNonNull; +import static uk.co.real_logic.sbe.ir.GenerationUtil.collectFields; +import static uk.co.real_logic.sbe.ir.GenerationUtil.collectGroups; +import static uk.co.real_logic.sbe.ir.GenerationUtil.collectVarData; + +/** + * A factory for creating a {@link FieldPrecedenceModel} for a message from its IR tokens. + */ +public final class PrecedenceChecks +{ + private static final Function SELECT_LATEST_VERSION_ONLY = versions -> + { + final OptionalInt max = versions.max(); + return max.isPresent() ? IntStream.of(max.getAsInt()) : IntStream.empty(); + }; + + private final Context context; + + private PrecedenceChecks(final Context context) + { + context.conclude(); + this.context = context; + } + + /** + * Creates a {@link FieldPrecedenceModel} for the given message tokens, + * unless precedence checks are disabled, in which case {@code null} is returned. + *

+ * Only the latest version of the message is considered when creating the model. + *

+ * + * @param stateClassName the name of the generated class that models the state of the encoder. + * @param msgTokens the tokens of the message. + * @return a {@link FieldPrecedenceModel} for the given message tokens or {@code null} if precedence checks + * are disabled. + */ + public FieldPrecedenceModel createEncoderModel( + final String stateClassName, + final List msgTokens) + { + return createModel(stateClassName, msgTokens, SELECT_LATEST_VERSION_ONLY); + } + + /** + * Creates a {@link FieldPrecedenceModel} for the given message tokens, + * unless precedence checks are disabled, in which case {@code null} is returned. + *

+ * All versions of the message are considered when creating the model. + *

+ * + * @param stateClassName the name of the generated class that models the state of the decoder. + * @param msgTokens the tokens of the message. + * @return a {@link FieldPrecedenceModel} for the given message tokens or {@code null} if precedence checks + * are disabled. + */ + public FieldPrecedenceModel createDecoderModel( + final String stateClassName, + final List msgTokens) + { + return createModel(stateClassName, msgTokens, Function.identity()); + } + + /** + * Creates a {@link FieldPrecedenceModel} for the given message tokens, + * unless precedence checks are disabled, in which case {@code null} is returned. + *

+ * All versions of the message are considered when creating the model. + *

+ * + * @param stateClassName the name of the generated class that models the state of the codec. + * @param msgTokens the tokens of the message. + * @return a {@link FieldPrecedenceModel} for the given message tokens or {@code null} if precedence checks + * are disabled. + */ + public FieldPrecedenceModel createCodecModel( + final String stateClassName, + final List msgTokens + ) + { + return createModel(stateClassName, msgTokens, Function.identity()); + } + + /** + * Returns the {@link Context} describing how precedence checks should be generated. + * + * @return the {@link Context} describing how precedence checks should be generated. + */ + public Context context() + { + return context; + } + + /** + * Creates a new {@link PrecedenceChecks} instance. + * + * @param context the {@link Context} describing how precedence checks should be generated. + * @return a new {@link PrecedenceChecks} instance. + */ + public static PrecedenceChecks newInstance(final Context context) + { + return new PrecedenceChecks(context); + } + + private FieldPrecedenceModel createModel( + final String stateClassName, + final List tokens, + final Function versionsSelector + ) + { + if (context.shouldGeneratePrecedenceChecks()) + { + final Token msgToken = tokens.get(0); + + final List messageBody = tokens.subList(1, tokens.size() - 1); + int i = 0; + + final List fields = new ArrayList<>(); + i = collectFields(messageBody, i, fields); + + final List groups = new ArrayList<>(); + i = collectGroups(messageBody, i, groups); + + final List varData = new ArrayList<>(); + collectVarData(messageBody, i, varData); + + return FieldPrecedenceModel.newInstance( + stateClassName, + msgToken, + fields, + groups, + varData, + versionsSelector); + } + + return null; + } + + /** + * The context describing how precedence checks should be generated. + */ + public static final class Context + { + private boolean shouldGeneratePrecedenceChecks; + private String precedenceChecksFlagName = "SBE_ENABLE_PRECEDENCE_CHECKS"; + private String precedenceChecksPropName = "sbe.enable.precedence.checks"; + + /** + * Returns {@code true} if precedence checks should be generated; {@code false} otherwise. + * + * @return {@code true} if precedence checks should be generated; {@code false} otherwise. + */ + public boolean shouldGeneratePrecedenceChecks() + { + return shouldGeneratePrecedenceChecks; + } + + /** + * Sets whether field precedence checks should be generated. + * + * @param shouldGeneratePrecedenceChecks {@code true} if precedence checks should be generated; + * {@code false} otherwise. + * @return this {@link Context} instance. + */ + public Context shouldGeneratePrecedenceChecks(final boolean shouldGeneratePrecedenceChecks) + { + this.shouldGeneratePrecedenceChecks = shouldGeneratePrecedenceChecks; + return this; + } + + /** + * Returns the name of the flag that can be used to enable precedence checks at runtime. + * + * @return the name of the flag that can be used to enable precedence checks at runtime. + */ + public String precedenceChecksFlagName() + { + return precedenceChecksFlagName; + } + + /** + * Sets the name of the flag that can be used to enable precedence checks at runtime. + * + * @param precedenceChecksFlagName the name of the flag that can be used to enable + * precedence checks at runtime. + * @return this {@link Context} instance. + */ + public Context precedenceChecksFlagName(final String precedenceChecksFlagName) + { + this.precedenceChecksFlagName = precedenceChecksFlagName; + return this; + } + + /** + * Returns the name of the system property that can be used to enable precedence checks + * at runtime. + * + * @return the name of the system property that can be used to enable precedence checks + * at runtime. + */ + public String precedenceChecksPropName() + { + return precedenceChecksPropName; + } + + /** + * Sets the name of the system property that can be used to enable precedence checks + * at runtime. + * + * @param precedenceChecksPropName the name of the system property that can be used to + * enable precedence checks at runtime. + * @return this {@link Context} instance. + */ + public Context precedenceChecksPropName(final String precedenceChecksPropName) + { + this.precedenceChecksPropName = precedenceChecksPropName; + return this; + } + + /** + * Validates this {@link Context} instance. + */ + public void conclude() + { + requireNonNull(precedenceChecksFlagName, "precedenceChecksFlagName"); + requireNonNull(precedenceChecksPropName, "precedenceChecksPropName"); + } + } +} diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java index 482335b269..c801c58057 100755 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java @@ -15,23 +15,24 @@ */ package uk.co.real_logic.sbe.generation.cpp; -import org.agrona.Strings; -import org.agrona.Verify; -import org.agrona.generation.OutputManager; import uk.co.real_logic.sbe.PrimitiveType; import uk.co.real_logic.sbe.generation.CodeGenerator; import uk.co.real_logic.sbe.generation.Generators; +import uk.co.real_logic.sbe.generation.common.FieldPrecedenceModel; +import uk.co.real_logic.sbe.generation.common.PrecedenceChecks; import uk.co.real_logic.sbe.ir.Encoding; import uk.co.real_logic.sbe.ir.Ir; import uk.co.real_logic.sbe.ir.Signal; import uk.co.real_logic.sbe.ir.Token; +import org.agrona.Strings; +import org.agrona.Verify; +import org.agrona.collections.MutableBoolean; +import org.agrona.generation.OutputManager; import java.io.IOException; import java.io.Writer; import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Formatter; -import java.util.List; +import java.util.*; import static uk.co.real_logic.sbe.generation.Generators.toLowerFirstChar; import static uk.co.real_logic.sbe.generation.Generators.toUpperFirstChar; @@ -45,12 +46,18 @@ @SuppressWarnings("MethodLength") public class CppGenerator implements CodeGenerator { + private static final boolean DISABLE_IMPLICIT_COPYING = Boolean.parseBoolean( + System.getProperty("sbe.cpp.disable.implicit.copying", "false")); private static final String BASE_INDENT = ""; private static final String INDENT = " "; + private static final String TWO_INDENT = INDENT + INDENT; + private static final String THREE_INDENT = TWO_INDENT + INDENT; private final Ir ir; private final OutputManager outputManager; private final boolean shouldDecodeUnknownEnumValues; + private final PrecedenceChecks precedenceChecks; + private final String precedenceChecksFlagName; /** * Create a new Go language {@link CodeGenerator}. @@ -60,12 +67,36 @@ public class CppGenerator implements CodeGenerator * @param outputManager for generating the codecs to. */ public CppGenerator(final Ir ir, final boolean shouldDecodeUnknownEnumValues, final OutputManager outputManager) + { + this( + ir, + shouldDecodeUnknownEnumValues, + PrecedenceChecks.newInstance(new PrecedenceChecks.Context()), + outputManager + ); + } + + /** + * Create a new Go language {@link CodeGenerator}. + * + * @param ir for the messages and types. + * @param shouldDecodeUnknownEnumValues generate support for unknown enum values when decoding. + * @param precedenceChecks whether and how to generate field precedence checks. + * @param outputManager for generating the codecs to. + */ + public CppGenerator( + final Ir ir, + final boolean shouldDecodeUnknownEnumValues, + final PrecedenceChecks precedenceChecks, + final OutputManager outputManager) { Verify.notNull(ir, "ir"); Verify.notNull(outputManager, "outputManager"); this.ir = ir; this.shouldDecodeUnknownEnumValues = shouldDecodeUnknownEnumValues; + this.precedenceChecks = precedenceChecks; + this.precedenceChecksFlagName = precedenceChecks.context().precedenceChecksFlagName(); this.outputManager = outputManager; } @@ -143,12 +174,11 @@ public void generate() throws IOException { final Token msgToken = tokens.get(0); final String className = formatClassName(msgToken.name()); + final String stateClassName = className + "::CodecState"; + final FieldPrecedenceModel fieldPrecedenceModel = precedenceChecks.createCodecModel(stateClassName, tokens); try (Writer out = outputManager.createOutput(className)) { - out.append(generateFileHeader(ir.namespaces(), className, typesToInclude)); - out.append(generateClassDeclaration(className)); - out.append(generateMessageFlyweightCode(className, msgToken)); final List messageBody = tokens.subList(1, tokens.size() - 1); int i = 0; @@ -162,20 +192,393 @@ public void generate() throws IOException final List varData = new ArrayList<>(); collectVarData(messageBody, i, varData); + out.append(generateFileHeader(ir.namespaces(), className, typesToInclude)); + out.append(generateClassDeclaration(className)); + out.append(generateMessageFlyweightCode(className, msgToken, fieldPrecedenceModel)); + out.append(generateFullyEncodedCheck(fieldPrecedenceModel)); + final StringBuilder sb = new StringBuilder(); - generateFields(sb, className, fields, BASE_INDENT); - generateGroups(sb, groups, BASE_INDENT); - generateVarData(sb, className, varData, BASE_INDENT); + generateFields(sb, className, fields, fieldPrecedenceModel, BASE_INDENT); + generateGroups(sb, groups, fieldPrecedenceModel, BASE_INDENT); + generateVarData(sb, className, varData, fieldPrecedenceModel, BASE_INDENT); generateDisplay(sb, msgToken.name(), fields, groups, varData); sb.append(generateMessageLength(groups, varData, BASE_INDENT)); sb.append("};\n"); + generateLookupTableDefinitions(sb, className, fieldPrecedenceModel); sb.append(CppUtil.closingBraces(ir.namespaces().length)).append("#endif\n"); out.append(sb); } } } - private void generateGroups(final StringBuilder sb, final List tokens, final String indent) + private CharSequence generateFullyEncodedCheck(final FieldPrecedenceModel fieldPrecedenceModel) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final String indent = " "; + final StringBuilder sb = new StringBuilder(); + sb.append("\n"); + + sb.append(indent).append("void checkEncodingIsComplete()\n") + .append(indent).append("{\n") + .append("#if defined(").append(precedenceChecksFlagName).append(")\n") + .append(indent).append(INDENT).append("switch (m_codecState)\n") + .append(indent).append(INDENT).append("{\n"); + + fieldPrecedenceModel.forEachTerminalEncoderState(state -> + { + sb.append(indent).append(TWO_INDENT).append("case ").append(stateCaseForSwitchCase(state)).append(":\n") + .append(indent).append(THREE_INDENT).append("return;\n"); + }); + + sb.append(indent).append(TWO_INDENT).append("default:\n") + .append(indent).append(THREE_INDENT) + .append("throw AccessOrderError(std::string(\"Not fully encoded, current state: \") +\n") + .append(indent).append(THREE_INDENT) + .append(INDENT).append("codecStateName(m_codecState) + \", allowed transitions: \" +\n") + .append(indent).append(THREE_INDENT) + .append(INDENT).append("codecStateTransitions(m_codecState));\n") + .append(indent).append(INDENT).append("}\n") + .append("#endif\n"); + + sb.append(indent).append("}\n\n"); + + return sb; + } + + private static String accessOrderListenerMethodName(final Token token) + { + return "on" + Generators.toUpperFirstChar(token.name()) + "Accessed"; + } + + private static String accessOrderListenerMethodName(final Token token, final String suffix) + { + return "on" + Generators.toUpperFirstChar(token.name()) + suffix + "Accessed"; + } + + private static void generateAccessOrderListenerMethod( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + final FieldPrecedenceModel.CodecInteraction fieldAccess = + fieldPrecedenceModel.interactionFactory().accessField(token); + + final String constDeclaration = canChangeState(fieldPrecedenceModel, fieldAccess) ? "" : " const"; + + sb.append("\n") + .append(indent).append("private:\n") + .append(indent).append(INDENT).append("void ").append(accessOrderListenerMethodName(token)).append("()") + .append(constDeclaration).append("\n") + .append(indent).append(INDENT).append("{\n"); + + generateAccessOrderListener( + sb, + indent + TWO_INDENT, + "access field", + fieldPrecedenceModel, + fieldAccess); + + sb.append(indent).append(INDENT).append("}\n\n") + .append(indent).append("public:"); + } + + private static boolean canChangeState( + final FieldPrecedenceModel fieldPrecedenceModel, + final FieldPrecedenceModel.CodecInteraction fieldAccess) + { + if (fieldAccess.isTopLevelBlockFieldAccess()) + { + return false; + } + + final MutableBoolean canChangeState = new MutableBoolean(false); + fieldPrecedenceModel.forEachTransition(fieldAccess, transition -> + { + if (!transition.alwaysEndsInStartState()) + { + canChangeState.set(true); + } + }); + + return canChangeState.get(); + } + + private CharSequence generateAccessOrderListenerCall( + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token, + final String... arguments) + { + return generateAccessOrderListenerCall( + fieldPrecedenceModel, + indent, + accessOrderListenerMethodName(token), + arguments); + } + + private CharSequence generateAccessOrderListenerCall( + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final String methodName, + final String... arguments) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + sb.append("#if defined(").append(precedenceChecksFlagName).append(")\n") + .append(indent).append(methodName).append("("); + + for (int i = 0; i < arguments.length; i++) + { + if (i > 0) + { + sb.append(", "); + } + sb.append(arguments[i]); + } + sb.append(");\n"); + + sb.append("#endif\n"); + + return sb; + } + + private static void generateAccessOrderListenerMethodForGroupWrap( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append("\n") + .append(indent).append("private:\n") + .append(indent).append(INDENT).append("void ").append(accessOrderListenerMethodName(token)) + .append("(std::uint64_t remaining, std::string action)\n") + .append(indent).append(INDENT).append("{\n") + .append(indent).append(TWO_INDENT).append("if (0 == remaining)\n") + .append(indent).append(TWO_INDENT).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction selectEmptyGroup = + fieldPrecedenceModel.interactionFactory().determineGroupIsEmpty(token); + + generateAccessOrderListener( + sb, + indent + THREE_INDENT, + "\" + action + \" count of repeating group", + fieldPrecedenceModel, + selectEmptyGroup); + + sb.append(indent).append(TWO_INDENT).append("}\n") + .append(indent).append(TWO_INDENT).append("else\n") + .append(indent).append(TWO_INDENT).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction selectNonEmptyGroup = + fieldPrecedenceModel.interactionFactory().determineGroupHasElements(token); + + generateAccessOrderListener( + sb, + indent + THREE_INDENT, + "\" + action + \" count of repeating group", + fieldPrecedenceModel, + selectNonEmptyGroup); + + sb.append(indent).append(TWO_INDENT).append("}\n") + .append(indent).append(INDENT).append("}\n\n") + .append(indent).append("public:"); + } + + private static void generateAccessOrderListenerMethodForVarDataLength( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append("\n") + .append(indent).append("private:\n") + .append(indent).append(INDENT).append("void ").append(accessOrderListenerMethodName(token, "Length")) + .append("() const\n") + .append(indent).append(INDENT).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction accessLength = + fieldPrecedenceModel.interactionFactory().accessVarDataLength(token); + + generateAccessOrderListener( + sb, + indent + TWO_INDENT, + "decode length of var data", + fieldPrecedenceModel, + accessLength); + + sb.append(indent).append(INDENT).append("}\n\n") + .append(indent).append("public:"); + } + + private static void generateAccessOrderListener( + final StringBuilder sb, + final String indent, + final String action, + final FieldPrecedenceModel fieldPrecedenceModel, + final FieldPrecedenceModel.CodecInteraction interaction) + { + if (interaction.isTopLevelBlockFieldAccess()) + { + sb.append(indent).append("if (codecState() == ") + .append(qualifiedStateCase(fieldPrecedenceModel.notWrappedState())) + .append(")\n") + .append(indent).append("{\n"); + generateAccessOrderException(sb, indent + INDENT, action, fieldPrecedenceModel, interaction); + sb.append(indent).append("}\n"); + } + else + { + sb.append(indent).append("switch (codecState())\n") + .append(indent).append("{\n"); + + fieldPrecedenceModel.forEachTransition(interaction, transitionGroup -> + { + + transitionGroup.forEachStartState(startState -> + { + sb.append(indent).append(INDENT) + .append("case ").append(stateCaseForSwitchCase(startState)).append(":\n"); + }); + + if (!transitionGroup.alwaysEndsInStartState()) + { + sb.append(indent).append(TWO_INDENT).append("codecState(") + .append(qualifiedStateCase(transitionGroup.endState())).append(");\n"); + } + + sb.append(indent).append(TWO_INDENT).append("break;\n"); + }); + + sb.append(indent).append(INDENT).append("default:\n"); + generateAccessOrderException(sb, indent + TWO_INDENT, action, fieldPrecedenceModel, interaction); + sb.append(indent).append("}\n"); + } + } + + private static void generateAccessOrderException( + final StringBuilder sb, + final String indent, + final String action, + final FieldPrecedenceModel fieldPrecedenceModel, + final FieldPrecedenceModel.CodecInteraction interaction) + { + sb.append(indent).append("throw AccessOrderError(") + .append("std::string(\"Illegal field access order. \") +\n") + .append(indent).append(INDENT) + .append("\"Cannot ").append(action).append(" \\\"").append(interaction.groupQualifiedName()) + .append("\\\" in state: \" + codecStateName(codecState()) +\n") + .append(indent).append(INDENT) + .append("\". Expected one of these transitions: [\" + codecStateTransitions(codecState()) +\n") + .append(indent).append(INDENT) + .append("\"]. Please see the diagram in the docs of the enum ") + .append(fieldPrecedenceModel.generatedRepresentationClassName()).append(".\");\n"); + } + + private static void generateAccessOrderListenerMethodForNextGroupElement( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append("\n"); + + sb.append(indent).append(INDENT).append("void onNextElementAccessed()\n") + .append(indent).append(INDENT).append("{\n") + .append(indent).append(TWO_INDENT).append("std::uint64_t remaining = m_count - m_index;\n") + .append(indent).append(TWO_INDENT).append("if (remaining > 1)\n") + .append(indent).append(TWO_INDENT).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction selectNextElementInGroup = + fieldPrecedenceModel.interactionFactory().moveToNextElement(token); + + generateAccessOrderListener( + sb, + indent + THREE_INDENT, + "access next element in repeating group", + fieldPrecedenceModel, + selectNextElementInGroup); + + sb.append(indent).append(TWO_INDENT).append("}\n") + .append(indent).append(TWO_INDENT).append("else if (1 == remaining)\n") + .append(indent).append(TWO_INDENT).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction selectLastElementInGroup = + fieldPrecedenceModel.interactionFactory().moveToLastElement(token); + + generateAccessOrderListener( + sb, + indent + THREE_INDENT, + "access next element in repeating group", + fieldPrecedenceModel, + selectLastElementInGroup); + + sb.append(indent).append(TWO_INDENT).append("}\n") + .append(indent).append(INDENT).append("}\n"); + } + + private static void generateAccessOrderListenerMethodForResetGroupCount( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append("\n") + .append(indent).append(INDENT).append("void onResetCountToIndex()\n") + .append(indent).append(INDENT).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction resetCountToIndex = + fieldPrecedenceModel.interactionFactory().resetCountToIndex(token); + + generateAccessOrderListener( + sb, + indent + TWO_INDENT, + "reset count of repeating group", + fieldPrecedenceModel, + resetCountToIndex); + + sb.append(indent).append(INDENT).append("}\n"); + } + + private void generateGroups( + final StringBuilder sb, + final List tokens, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent) { for (int i = 0, size = tokens.size(); i < size; i++) { @@ -189,7 +592,7 @@ private void generateGroups(final StringBuilder sb, final List tokens, fi final Token numInGroupToken = Generators.findFirst("numInGroup", tokens, i); final String cppTypeForNumInGroup = cppTypeName(numInGroupToken.encoding().primitiveType()); - generateGroupClassHeader(sb, groupName, tokens, i, indent + INDENT); + generateGroupClassHeader(sb, groupName, groupToken, tokens, fieldPrecedenceModel, i, indent + INDENT); ++i; final int groupHeaderTokenCount = tokens.get(i).componentTokenCount(); @@ -197,26 +600,32 @@ private void generateGroups(final StringBuilder sb, final List tokens, fi final List fields = new ArrayList<>(); i = collectFields(tokens, i, fields); - generateFields(sb, formatClassName(groupName), fields, indent + INDENT); + generateFields(sb, formatClassName(groupName), fields, fieldPrecedenceModel, indent + INDENT); final List groups = new ArrayList<>(); i = collectGroups(tokens, i, groups); - generateGroups(sb, groups, indent + INDENT); + generateGroups(sb, groups, fieldPrecedenceModel, indent + INDENT); final List varData = new ArrayList<>(); i = collectVarData(tokens, i, varData); - generateVarData(sb, formatClassName(groupName), varData, indent + INDENT); + generateVarData(sb, formatClassName(groupName), varData, fieldPrecedenceModel, indent + INDENT); sb.append(generateGroupDisplay(groupName, fields, groups, varData, indent + INDENT + INDENT)); sb.append(generateMessageLength(groups, varData, indent + INDENT + INDENT)); sb.append(indent).append(" };\n"); - generateGroupProperty(sb, groupName, groupToken, cppTypeForNumInGroup, indent); + generateGroupProperty(sb, groupName, groupToken, cppTypeForNumInGroup, fieldPrecedenceModel, indent); } } - private static void generateGroupClassHeader( - final StringBuilder sb, final String groupName, final List tokens, final int index, final String indent) + private void generateGroupClassHeader( + final StringBuilder sb, + final String groupName, + final Token groupToken, + final List tokens, + final FieldPrecedenceModel fieldPrecedenceModel, + final int index, + final String indent) { final String dimensionsClassName = formatClassName(tokens.get(index + 1).name()); final int dimensionHeaderLength = tokens.get(index + 1).encodedLength(); @@ -226,6 +635,8 @@ private static void generateGroupClassHeader( final String cppTypeBlockLength = cppTypeName(blockLengthToken.encoding().primitiveType()); final String cppTypeNumInGroup = cppTypeName(numInGroupToken.encoding().primitiveType()); + final String groupClassName = formatClassName(groupName); + new Formatter(sb).format("\n" + indent + "class %1$s\n" + indent + "{\n" + @@ -243,17 +654,50 @@ private static void generateGroupClassHeader( indent + " SBE_NODISCARD std::uint64_t *sbePositionPtr() SBE_NOEXCEPT\n" + indent + " {\n" + indent + " return m_positionPtr;\n" + - indent + " }\n\n" + + indent + " }\n\n", + groupClassName); - indent + "public:\n", - formatClassName(groupName)); + if (null != fieldPrecedenceModel) + { + new Formatter(sb).format( + indent + " CodecState *m_codecStatePtr = nullptr;\n\n" + + + indent + " CodecState codecState() const SBE_NOEXCEPT\n" + + indent + " {\n" + + indent + " return *m_codecStatePtr;\n" + + indent + " }\n\n" + + + indent + " CodecState *codecStatePtr()\n" + + indent + " {\n" + + indent + " return m_codecStatePtr;\n" + + indent + " }\n\n" + + + indent + " void codecState(CodecState codecState)\n" + + indent + " {\n" + + indent + " *m_codecStatePtr = codecState;\n" + + indent + " }\n\n" + ); + } + + sb.append(generateHiddenCopyConstructor(indent + " ", groupClassName)); + + final String codecStateParameter = null == fieldPrecedenceModel ? + ")\n" : + ",\n " + indent + " CodecState *codecState)\n"; + + final String codecStateAssignment = null == fieldPrecedenceModel ? + "" : + indent + " m_codecStatePtr = codecState;\n"; new Formatter(sb).format( + indent + "public:\n" + + indent + " %5$s() = default;\n\n" + + indent + " inline void wrapForDecode(\n" + indent + " char *buffer,\n" + indent + " std::uint64_t *pos,\n" + indent + " const std::uint64_t actingVersion,\n" + - indent + " const std::uint64_t bufferLength)\n" + + indent + " const std::uint64_t bufferLength%3$s" + indent + " {\n" + indent + " %2$s dimensions(buffer, *pos, bufferLength, actingVersion);\n" + indent + " m_buffer = buffer;\n" + @@ -265,8 +709,13 @@ private static void generateGroupClassHeader( indent + " m_initialPosition = *pos;\n" + indent + " m_positionPtr = pos;\n" + indent + " *m_positionPtr = *m_positionPtr + %1$d;\n" + + "%4$s" + indent + " }\n", - dimensionHeaderLength, dimensionsClassName); + dimensionHeaderLength, + dimensionsClassName, + codecStateParameter, + codecStateAssignment, + groupClassName); final long minCount = numInGroupToken.encoding().applicableMinValue().longValue(); final String minCheck = minCount > 0 ? "count < " + minCount + " || " : ""; @@ -277,7 +726,7 @@ private static void generateGroupClassHeader( indent + " const %3$s count,\n" + indent + " std::uint64_t *pos,\n" + indent + " const std::uint64_t actingVersion,\n" + - indent + " const std::uint64_t bufferLength)\n" + + indent + " const std::uint64_t bufferLength%8$s" + indent + " {\n" + indent + "#if defined(__GNUG__) && !defined(__clang__)\n" + indent + "#pragma GCC diagnostic push\n" + @@ -302,6 +751,7 @@ private static void generateGroupClassHeader( indent + " m_initialPosition = *pos;\n" + indent + " m_positionPtr = pos;\n" + indent + " *m_positionPtr = *m_positionPtr + %4$d;\n" + + "%9$s" + indent + " }\n", cppTypeBlockLength, blockLength, @@ -309,7 +759,43 @@ private static void generateGroupClassHeader( dimensionHeaderLength, minCheck, numInGroupToken.encoding().applicableMaxValue().longValue(), - dimensionsClassName); + dimensionsClassName, + codecStateParameter, + codecStateAssignment); + + if (groupToken.version() > 0) + { + final String codecStateNullAssignment = null == fieldPrecedenceModel ? + "" : + indent + " m_codecStatePtr = nullptr;\n"; + + new Formatter(sb).format( + indent + " inline void notPresent(std::uint64_t actingVersion)\n" + + indent + " {\n" + + indent + " m_buffer = nullptr;\n" + + indent + " m_bufferLength = 0;\n" + + indent + " m_blockLength = 0;\n" + + indent + " m_count = 0;\n" + + indent + " m_index = 0;\n" + + indent + " m_actingVersion = actingVersion;\n" + + indent + " m_initialPosition = 0;\n" + + indent + " m_positionPtr = nullptr;\n" + + "%1$s" + + indent + " }\n", + codecStateNullAssignment); + } + + + if (null != fieldPrecedenceModel) + { + sb.append("\n").append(indent).append("private:"); + generateAccessOrderListenerMethodForNextGroupElement(sb, fieldPrecedenceModel, indent, groupToken); + generateAccessOrderListenerMethodForResetGroupCount(sb, fieldPrecedenceModel, indent, groupToken); + sb.append("\n").append(indent).append("public:"); + } + + final CharSequence onNextAccessOrderCall = null == fieldPrecedenceModel ? "" : + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + TWO_INDENT, "onNextElementAccessed"); new Formatter(sb).format("\n" + indent + " static SBE_CONSTEXPR std::uint64_t sbeHeaderSize() SBE_NOEXCEPT\n" + @@ -358,6 +844,7 @@ private static void generateGroupClassHeader( indent + " {\n" + indent + " throw std::runtime_error(\"index >= count [E108]\");\n" + indent + " }\n" + + "%4$s" + indent + " m_offset = *m_positionPtr;\n" + indent + " if (SBE_BOUNDS_CHECK_EXPECT(((m_offset + m_blockLength) > m_bufferLength), false))\n" + indent + " {\n" + @@ -370,11 +857,13 @@ private static void generateGroupClassHeader( indent + " }\n", dimensionHeaderLength, blockLength, - formatClassName(groupName)); + groupClassName, + onNextAccessOrderCall); sb.append("\n") .append(indent).append(" inline std::uint64_t resetCountToIndex()\n") .append(indent).append(" {\n") + .append(generateAccessOrderListenerCall(fieldPrecedenceModel, indent + TWO_INDENT, "onResetCountToIndex")) .append(indent).append(" m_count = m_index;\n") .append(indent).append(" ").append(dimensionsClassName) .append(" dimensions(m_buffer, m_initialPosition, m_bufferLength, m_actingVersion);\n") @@ -394,11 +883,12 @@ private static void generateGroupClassHeader( .append(indent).append(" }\n\n"); } - private static void generateGroupProperty( + private void generateGroupProperty( final StringBuilder sb, final String groupName, final Token token, final String cppTypeForNumInGroup, + final FieldPrecedenceModel fieldPrecedenceModel, final String indent) { final String className = formatClassName(groupName); @@ -420,25 +910,76 @@ private static void generateGroupProperty( groupName, token.id()); - new Formatter(sb).format("\n" + - indent + " SBE_NODISCARD inline %1$s &%2$s()\n" + - indent + " {\n" + - indent + " m_%2$s.wrapForDecode(m_buffer, sbePositionPtr(), m_actingVersion, m_bufferLength);\n" + - indent + " return m_%2$s;\n" + - indent + " }\n", - className, - propertyName); + if (null != fieldPrecedenceModel) + { + generateAccessOrderListenerMethodForGroupWrap( + sb, + fieldPrecedenceModel, + indent, + token + ); + } + + final String codecStateArgument = null == fieldPrecedenceModel ? "" : ", codecStatePtr()"; + + final CharSequence onDecodeAccessOrderCall = null == fieldPrecedenceModel ? "" : + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + TWO_INDENT, token, + "m_" + propertyName + ".count()", "\"decode\""); + + if (token.version() > 0) + { + new Formatter(sb).format("\n" + + indent + " SBE_NODISCARD inline %1$s &%2$s()\n" + + indent + " {\n" + + indent + " if (m_actingVersion < %5$du)\n" + + indent + " {\n" + + indent + " m_%2$s.notPresent(m_actingVersion);\n" + + indent + " return m_%2$s;\n" + + indent + " }\n\n" + + + indent + " m_%2$s.wrapForDecode(" + + "m_buffer, sbePositionPtr(), m_actingVersion, m_bufferLength%3$s);\n" + + "%4$s" + + indent + " return m_%2$s;\n" + + indent + " }\n", + className, + propertyName, + codecStateArgument, + onDecodeAccessOrderCall, + token.version()); + } + else + { + new Formatter(sb).format("\n" + + indent + " SBE_NODISCARD inline %1$s &%2$s()\n" + + indent + " {\n" + + indent + " m_%2$s.wrapForDecode(" + + "m_buffer, sbePositionPtr(), m_actingVersion, m_bufferLength%3$s);\n" + + "%4$s" + + indent + " return m_%2$s;\n" + + indent + " }\n", + className, + propertyName, + codecStateArgument, + onDecodeAccessOrderCall); + } + + final CharSequence onEncodeAccessOrderCall = null == fieldPrecedenceModel ? "" : + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + TWO_INDENT, token, "count", "\"encode\""); new Formatter(sb).format("\n" + indent + " %1$s &%2$sCount(const %3$s count)\n" + indent + " {\n" + indent + " m_%2$s.wrapForEncode(" + - "m_buffer, count, sbePositionPtr(), m_actingVersion, m_bufferLength);\n" + + "m_buffer, count, sbePositionPtr(), m_actingVersion, m_bufferLength%4$s);\n" + + "%5$s" + indent + " return m_%2$s;\n" + indent + " }\n", className, propertyName, - cppTypeForNumInGroup); + cppTypeForNumInGroup, + codecStateArgument, + onEncodeAccessOrderCall); final int version = token.version(); final String versionCheck = 0 == version ? @@ -458,7 +999,11 @@ private static void generateGroupProperty( } private void generateVarData( - final StringBuilder sb, final String className, final List tokens, final String indent) + final StringBuilder sb, + final String className, + final List tokens, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent) { for (int i = 0, size = tokens.size(); i < size;) { @@ -480,12 +1025,39 @@ private void generateVarData( generateFieldMetaAttributeMethod(sb, token, indent); generateVarDataDescriptors( - sb, token, propertyName, characterEncoding, lengthToken, lengthOfLengthField, lengthCppType, indent); + sb, token, propertyName, characterEncoding, lengthOfLengthField, indent); + + generateAccessOrderListenerMethodForVarDataLength(sb, fieldPrecedenceModel, indent, token); + + final CharSequence lengthAccessListenerCall = generateAccessOrderListenerCall( + fieldPrecedenceModel, indent + TWO_INDENT, + accessOrderListenerMethodName(token, "Length")); + + new Formatter(sb).format("\n" + + indent + " SBE_NODISCARD %4$s %1$sLength() const\n" + + indent + " {\n" + + "%2$s" + + "%5$s" + + indent + " %4$s length;\n" + + indent + " std::memcpy(&length, m_buffer + sbePosition(), sizeof(%4$s));\n" + + indent + " return %3$s(length);\n" + + indent + " }\n", + toLowerFirstChar(propertyName), + generateArrayFieldNotPresentCondition(token.version(), BASE_INDENT), + formatByteOrderEncoding(lengthToken.encoding().byteOrder(), lengthToken.encoding().primitiveType()), + lengthCppType, + lengthAccessListenerCall); + + generateAccessOrderListenerMethod(sb, fieldPrecedenceModel, indent, token); + + final CharSequence accessOrderListenerCall = + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + TWO_INDENT, token); new Formatter(sb).format("\n" + indent + " std::uint64_t skip%1$s()\n" + indent + " {\n" + "%2$s" + + "%6$s" + indent + " std::uint64_t lengthOfLengthField = %3$d;\n" + indent + " std::uint64_t lengthPosition = sbePosition();\n" + indent + " %5$s lengthFieldValue;\n" + @@ -498,12 +1070,14 @@ private void generateVarData( generateArrayFieldNotPresentCondition(token.version(), indent), lengthOfLengthField, lengthByteOrderStr, - lengthCppType); + lengthCppType, + accessOrderListenerCall); new Formatter(sb).format("\n" + indent + " SBE_NODISCARD const char *%1$s()\n" + indent + " {\n" + "%2$s" + + "%6$s" + indent + " %4$s lengthFieldValue;\n" + indent + " std::memcpy(&lengthFieldValue, m_buffer + sbePosition(), sizeof(%4$s));\n" + indent + " const char *fieldPtr = m_buffer + sbePosition() + %3$d;\n" + @@ -514,12 +1088,14 @@ private void generateVarData( generateTypeFieldNotPresentCondition(token.version(), indent), lengthOfLengthField, lengthCppType, - lengthByteOrderStr); + lengthByteOrderStr, + accessOrderListenerCall); new Formatter(sb).format("\n" + indent + " std::uint64_t get%1$s(char *dst, const std::uint64_t length)\n" + indent + " {\n" + "%2$s" + + "%6$s" + indent + " std::uint64_t lengthOfLengthField = %3$d;\n" + indent + " std::uint64_t lengthPosition = sbePosition();\n" + indent + " sbePosition(lengthPosition + lengthOfLengthField);\n" + @@ -536,11 +1112,13 @@ private void generateVarData( generateArrayFieldNotPresentCondition(token.version(), indent), lengthOfLengthField, lengthByteOrderStr, - lengthCppType); + lengthCppType, + accessOrderListenerCall); new Formatter(sb).format("\n" + indent + " %5$s &put%1$s(const char *src, const %3$s length)\n" + indent + " {\n" + + "%6$s" + indent + " std::uint64_t lengthOfLengthField = %2$d;\n" + indent + " std::uint64_t lengthPosition = sbePosition();\n" + indent + " %3$s lengthFieldValue = %4$s(length);\n" + @@ -558,12 +1136,14 @@ private void generateVarData( lengthOfLengthField, lengthCppType, lengthByteOrderStr, - className); + className, + accessOrderListenerCall); new Formatter(sb).format("\n" + indent + " std::string get%1$sAsString()\n" + indent + " {\n" + "%2$s" + + "%6$s" + indent + " std::uint64_t lengthOfLengthField = %3$d;\n" + indent + " std::uint64_t lengthPosition = sbePosition();\n" + indent + " sbePosition(lengthPosition + lengthOfLengthField);\n" + @@ -579,15 +1159,17 @@ private void generateVarData( generateStringNotPresentCondition(token.version(), indent), lengthOfLengthField, lengthByteOrderStr, - lengthCppType); + lengthCppType, + accessOrderListenerCall); - generateJsonEscapedStringGetter(sb, token, indent, propertyName); + generateJsonEscapedStringGetter(sb, token, indent, propertyName, accessOrderListenerCall); new Formatter(sb).format("\n" + indent + " #if __cplusplus >= 201703L\n" + indent + " std::string_view get%1$sAsStringView()\n" + indent + " {\n" + "%2$s" + + "%6$s" + indent + " std::uint64_t lengthOfLengthField = %3$d;\n" + indent + " std::uint64_t lengthPosition = sbePosition();\n" + indent + " sbePosition(lengthPosition + lengthOfLengthField);\n" + @@ -604,7 +1186,8 @@ private void generateVarData( generateStringViewNotPresentCondition(token.version(), indent), lengthOfLengthField, lengthByteOrderStr, - lengthCppType); + lengthCppType, + accessOrderListenerCall); new Formatter(sb).format("\n" + indent + " %1$s &put%2$s(const std::string &str)\n" + @@ -645,9 +1228,7 @@ private void generateVarDataDescriptors( final Token token, final String propertyName, final String characterEncoding, - final Token lengthToken, final Integer sizeOfLengthField, - final String lengthCppType, final String indent) { new Formatter(sb).format("\n" + @@ -687,19 +1268,6 @@ private void generateVarDataDescriptors( indent + " }\n", toLowerFirstChar(propertyName), sizeOfLengthField); - - new Formatter(sb).format("\n" + - indent + " SBE_NODISCARD %4$s %1$sLength() const\n" + - indent + " {\n" + - "%2$s" + - indent + " %4$s length;\n" + - indent + " std::memcpy(&length, m_buffer + sbePosition(), sizeof(%4$s));\n" + - indent + " return %3$s(length);\n" + - indent + " }\n", - toLowerFirstChar(propertyName), - generateArrayFieldNotPresentCondition(version, BASE_INDENT), - formatByteOrderEncoding(lengthToken.encoding().byteOrder(), lengthToken.encoding().primitiveType()), - lengthCppType); } private void generateChoiceSet(final List tokens) throws IOException @@ -1226,19 +1794,21 @@ private CharSequence generateCompositePropertyElements( switch (fieldToken.signal()) { case ENCODING: - generatePrimitiveProperty(sb, containingClassName, propertyName, fieldToken, fieldToken, indent); + generatePrimitiveProperty(sb, containingClassName, propertyName, fieldToken, fieldToken, + null, indent); break; case BEGIN_ENUM: - generateEnumProperty(sb, containingClassName, fieldToken, propertyName, fieldToken, indent); + generateEnumProperty(sb, containingClassName, fieldToken, propertyName, fieldToken, + null, indent); break; case BEGIN_SET: - generateBitsetProperty(sb, propertyName, fieldToken, indent); + generateBitsetProperty(sb, propertyName, fieldToken, fieldToken, null, indent); break; case BEGIN_COMPOSITE: - generateCompositeProperty(sb, propertyName, fieldToken, indent); + generateCompositeProperty(sb, propertyName, fieldToken, fieldToken, null, indent); break; default: @@ -1257,6 +1827,7 @@ private void generatePrimitiveProperty( final String propertyName, final Token propertyToken, final Token encodingToken, + final FieldPrecedenceModel fieldPrecedenceModel, final String indent) { generatePrimitiveFieldMetaData(sb, propertyName, encodingToken, indent); @@ -1268,7 +1839,7 @@ private void generatePrimitiveProperty( else { generatePrimitivePropertyMethods( - sb, containingClassName, propertyName, propertyToken, encodingToken, indent); + sb, containingClassName, propertyName, propertyToken, encodingToken, fieldPrecedenceModel, indent); } } @@ -1278,16 +1849,19 @@ private void generatePrimitivePropertyMethods( final String propertyName, final Token propertyToken, final Token encodingToken, + final FieldPrecedenceModel fieldPrecedenceModel, final String indent) { final int arrayLength = encodingToken.arrayLength(); if (arrayLength == 1) { - generateSingleValueProperty(sb, containingClassName, propertyName, propertyToken, encodingToken, indent); + generateSingleValueProperty(sb, containingClassName, propertyName, propertyToken, encodingToken, + fieldPrecedenceModel, indent); } else if (arrayLength > 1) { - generateArrayProperty(sb, containingClassName, propertyName, propertyToken, encodingToken, indent); + generateArrayProperty(sb, containingClassName, propertyName, propertyToken, encodingToken, + fieldPrecedenceModel, indent); } } @@ -1415,42 +1989,59 @@ private CharSequence generateStoreValue( return sb; } + private static String noexceptDeclaration(final FieldPrecedenceModel fieldPrecedenceModel) + { + return fieldPrecedenceModel == null ? " SBE_NOEXCEPT" : ""; + } + private void generateSingleValueProperty( final StringBuilder sb, final String containingClassName, final String propertyName, final Token propertyToken, final Token encodingToken, + final FieldPrecedenceModel fieldPrecedenceModel, final String indent) { final PrimitiveType primitiveType = encodingToken.encoding().primitiveType(); final String cppTypeName = cppTypeName(primitiveType); final int offset = encodingToken.offset(); + final CharSequence accessOrderListenerCall = + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + TWO_INDENT, propertyToken); + + final String noexceptDeclaration = noexceptDeclaration(fieldPrecedenceModel); + new Formatter(sb).format("\n" + - indent + " SBE_NODISCARD %1$s %2$s() const SBE_NOEXCEPT\n" + + indent + " SBE_NODISCARD %1$s %2$s() const%6$s\n" + indent + " {\n" + "%3$s" + "%4$s" + + "%5$s" + indent + " }\n", cppTypeName, propertyName, generateFieldNotPresentCondition(propertyToken.version(), encodingToken.encoding(), indent), - generateLoadValue(primitiveType, Integer.toString(offset), encodingToken.encoding().byteOrder(), indent)); + accessOrderListenerCall, + generateLoadValue(primitiveType, Integer.toString(offset), encodingToken.encoding().byteOrder(), indent), + noexceptDeclaration); final CharSequence storeValue = generateStoreValue( primitiveType, "", Integer.toString(offset), encodingToken.encoding().byteOrder(), indent); new Formatter(sb).format("\n" + - indent + " %1$s &%2$s(const %3$s value) SBE_NOEXCEPT\n" + + indent + " %1$s &%2$s(const %3$s value)%6$s\n" + indent + " {\n" + "%4$s" + + "%5$s" + indent + " return *this;\n" + indent + " }\n", formatClassName(containingClassName), propertyName, cppTypeName, - storeValue); + storeValue, + accessOrderListenerCall, + noexceptDeclaration); } private void generateArrayProperty( @@ -1459,12 +2050,18 @@ private void generateArrayProperty( final String propertyName, final Token propertyToken, final Token encodingToken, + final FieldPrecedenceModel fieldPrecedenceModel, final String indent) { final PrimitiveType primitiveType = encodingToken.encoding().primitiveType(); final String cppTypeName = cppTypeName(primitiveType); final int offset = encodingToken.offset(); + final CharSequence accessOrderListenerCall = + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + TWO_INDENT, propertyToken); + + final String noexceptDeclaration = noexceptDeclaration(fieldPrecedenceModel); + final int arrayLength = encodingToken.arrayLength(); new Formatter(sb).format("\n" + indent + " static SBE_CONSTEXPR std::uint64_t %1$sLength() SBE_NOEXCEPT\n" + @@ -1475,24 +2072,30 @@ private void generateArrayProperty( arrayLength); new Formatter(sb).format("\n" + - indent + " SBE_NODISCARD const char *%1$s() const SBE_NOEXCEPT\n" + + indent + " SBE_NODISCARD const char *%1$s() const%5$s\n" + indent + " {\n" + "%2$s" + + "%4$s" + indent + " return m_buffer + m_offset + %3$d;\n" + indent + " }\n", propertyName, generateTypeFieldNotPresentCondition(propertyToken.version(), indent), - offset); + offset, + accessOrderListenerCall, + noexceptDeclaration); new Formatter(sb).format("\n" + - indent + " SBE_NODISCARD char *%1$s() SBE_NOEXCEPT\n" + + indent + " SBE_NODISCARD char *%1$s()%5$s\n" + indent + " {\n" + "%2$s" + + "%4$s" + indent + " return m_buffer + m_offset + %3$d;\n" + indent + " }\n", propertyName, generateTypeFieldNotPresentCondition(propertyToken.version(), indent), - offset); + offset, + accessOrderListenerCall, + noexceptDeclaration); final CharSequence loadValue = generateLoadValue( primitiveType, @@ -1508,13 +2111,15 @@ private void generateArrayProperty( indent + " throw std::runtime_error(\"index out of range for %2$s [E104]\");\n" + indent + " }\n\n" + "%4$s" + + "%6$s" + "%5$s" + indent + " }\n", cppTypeName, propertyName, arrayLength, generateFieldNotPresentCondition(propertyToken.version(), encodingToken.encoding(), indent), - loadValue); + loadValue, + accessOrderListenerCall); final CharSequence storeValue = generateStoreValue( primitiveType, @@ -1531,6 +2136,7 @@ private void generateArrayProperty( indent + " throw std::runtime_error(\"index out of range for %2$s [E105]\");\n" + indent + " }\n\n" + + "%6$s" + "%5$s" + indent + " return *this;\n" + indent + " }\n", @@ -1538,7 +2144,8 @@ private void generateArrayProperty( propertyName, cppTypeName, arrayLength, - storeValue); + storeValue, + accessOrderListenerCall); new Formatter(sb).format("\n" + indent + " std::uint64_t get%1$s(char *const dst, const std::uint64_t length) const\n" + @@ -1549,6 +2156,7 @@ private void generateArrayProperty( indent + " }\n\n" + "%3$s" + + "%6$s" + indent + " std::memcpy(dst, m_buffer + m_offset + %4$d, " + "sizeof(%5$s) * static_cast(length));\n" + indent + " return length;\n" + @@ -1557,11 +2165,13 @@ private void generateArrayProperty( arrayLength, generateArrayFieldNotPresentCondition(propertyToken.version(), indent), offset, - cppTypeName); + cppTypeName, + accessOrderListenerCall); new Formatter(sb).format("\n" + - indent + " %1$s &put%2$s(const char *const src) SBE_NOEXCEPT\n" + + indent + " %1$s &put%2$s(const char *const src)%7$s\n" + indent + " {\n" + + "%6$s" + indent + " std::memcpy(m_buffer + m_offset + %3$d, src, sizeof(%4$s) * %5$d);\n" + indent + " return *this;\n" + indent + " }\n", @@ -1569,7 +2179,9 @@ private void generateArrayProperty( toUpperFirstChar(propertyName), offset, cppTypeName, - arrayLength); + arrayLength, + accessOrderListenerCall, + noexceptDeclaration); if (arrayLength > 1 && arrayLength <= 4) { @@ -1588,8 +2200,9 @@ private void generateArrayProperty( } } - sb.append(") SBE_NOEXCEPT\n"); + sb.append(")").append(noexceptDeclaration).append("\n"); sb.append(indent).append(" {\n"); + sb.append(accessOrderListenerCall); for (int i = 0; i < arrayLength; i++) { @@ -1611,6 +2224,8 @@ private void generateArrayProperty( new Formatter(sb).format("\n" + indent + " SBE_NODISCARD std::string get%1$sAsString() const\n" + indent + " {\n" + + "%4$s" + + "%5$s" + indent + " const char *buffer = m_buffer + m_offset + %2$d;\n" + indent + " std::size_t length = 0;\n\n" + @@ -1621,14 +2236,18 @@ private void generateArrayProperty( indent + " }\n", toUpperFirstChar(propertyName), offset, - arrayLength); + arrayLength, + generateStringNotPresentCondition(propertyToken.version(), indent), + accessOrderListenerCall); - generateJsonEscapedStringGetter(sb, encodingToken, indent, propertyName); + generateJsonEscapedStringGetter(sb, encodingToken, indent, propertyName, accessOrderListenerCall); new Formatter(sb).format("\n" + indent + " #if __cplusplus >= 201703L\n" + - indent + " SBE_NODISCARD std::string_view get%1$sAsStringView() const SBE_NOEXCEPT\n" + + indent + " SBE_NODISCARD std::string_view get%1$sAsStringView() const%6$s\n" + indent + " {\n" + + "%4$s" + + "%5$s" + indent + " const char *buffer = m_buffer + m_offset + %2$d;\n" + indent + " std::size_t length = 0;\n\n" + @@ -1640,7 +2259,10 @@ private void generateArrayProperty( indent + " #endif\n", toUpperFirstChar(propertyName), offset, - arrayLength); + arrayLength, + generateStringViewNotPresentCondition(propertyToken.version(), indent), + accessOrderListenerCall, + noexceptDeclaration); new Formatter(sb).format("\n" + indent + " #if __cplusplus >= 201703L\n" + @@ -1652,6 +2274,7 @@ private void generateArrayProperty( indent + " throw std::runtime_error(\"string too large for put%2$s [E106]\");\n" + indent + " }\n\n" + + "%5$s" + indent + " std::memcpy(m_buffer + m_offset + %3$d, str.data(), srcLength);\n" + indent + " for (std::size_t start = srcLength; start < %4$d; ++start)\n" + indent + " {\n" + @@ -1669,6 +2292,7 @@ private void generateArrayProperty( indent + " throw std::runtime_error(\"string too large for put%2$s [E106]\");\n" + indent + " }\n\n" + + "%5$s" + indent + " std::memcpy(m_buffer + m_offset + %3$d, str.c_str(), srcLength);\n" + indent + " for (std::size_t start = srcLength; start < %4$d; ++start)\n" + indent + " {\n" + @@ -1681,17 +2305,23 @@ private void generateArrayProperty( containingClassName, toUpperFirstChar(propertyName), offset, - arrayLength); + arrayLength, + accessOrderListenerCall); } } private void generateJsonEscapedStringGetter( - final StringBuilder sb, final Token token, final String indent, final String propertyName) + final StringBuilder sb, + final Token token, + final String indent, + final String propertyName, + final CharSequence accessOrderListenerCall) { new Formatter(sb).format("\n" + indent + " std::string get%1$sAsJsonEscapedString()\n" + indent + " {\n" + "%2$s" + + "%3$s" + indent + " std::ostringstream oss;\n" + indent + " std::string s = get%1$sAsString();\n\n" + indent + " for (const auto c : s)\n" + @@ -1720,7 +2350,8 @@ private void generateJsonEscapedStringGetter( indent + " return oss.str();\n" + indent + " }\n", toUpperFirstChar(propertyName), - generateStringNotPresentCondition(token.version(), indent)); + generateStringNotPresentCondition(token.version(), indent), + accessOrderListenerCall); } private void generateConstPropertyMethods( @@ -1807,7 +2438,7 @@ private void generateConstPropertyMethods( values, constantValue.length); - generateJsonEscapedStringGetter(sb, token, indent, propertyName); + generateJsonEscapedStringGetter(sb, token, indent, propertyName, ""); } private CharSequence generateFixedFlyweightCode(final String className, final int size) @@ -1821,6 +2452,7 @@ private CharSequence generateFixedFlyweightCode(final String className, final in " std::uint64_t m_bufferLength = 0;\n" + " std::uint64_t m_offset = 0;\n" + " std::uint64_t m_actingVersion = 0;\n\n" + + "%7$s" + "public:\n" + " enum MetaAttribute\n" + @@ -1879,7 +2511,17 @@ private CharSequence generateFixedFlyweightCode(final String className, final in " const std::uint64_t actingVersion,\n" + " const std::uint64_t bufferLength)\n" + " {\n" + - " return *this = %1$s(buffer, offset, bufferLength, actingVersion);\n" + + " m_buffer = buffer;\n" + + " m_bufferLength = bufferLength;\n" + + " m_offset = offset;\n" + + " m_actingVersion = actingVersion;\n\n" + + + " if (SBE_BOUNDS_CHECK_EXPECT(((m_offset + %2$s) > m_bufferLength), false))\n" + + " {\n" + + " throw std::runtime_error(\"buffer too short for flyweight [E107]\");\n" + + " }\n\n" + + + " return *this;\n" + " }\n\n" + " SBE_NODISCARD static SBE_CONSTEXPR std::uint64_t encodedLength() SBE_NOEXCEPT\n" + @@ -1926,14 +2568,53 @@ private CharSequence generateFixedFlyweightCode(final String className, final in schemaIdType, generateLiteral(ir.headerStructure().schemaIdType(), Integer.toString(ir.id())), schemaVersionType, - generateLiteral(ir.headerStructure().schemaVersionType(), Integer.toString(ir.version()))); + generateLiteral(ir.headerStructure().schemaVersionType(), Integer.toString(ir.version())), + generateHiddenCopyConstructor(" ", className)); + } + + private static String generateHiddenCopyConstructor(final String indent, final String className) + { + final String ctorAndCopyAssignmentDeletion = String.format( + "#if __cplusplus >= 201103L\n" + + "%1$s%2$s(const %2$s&) = delete;\n" + + "%1$s%2$s& operator=(const %2$s&) = delete;\n" + + "#else\n" + + "%1$s%2$s(const %2$s&);\n" + + "%1$s%2$s& operator=(const %2$s&);\n" + + "#endif\n\n", + indent, className); + + return DISABLE_IMPLICIT_COPYING ? ctorAndCopyAssignmentDeletion : ""; } - private static CharSequence generateConstructorsAndOperators(final String className) + private static CharSequence generateConstructorsAndOperators( + final String className, + final FieldPrecedenceModel fieldPrecedenceModel) { + final String constructorWithCodecState = null == fieldPrecedenceModel ? "" : String.format( + " %1$s(\n" + + " char *buffer,\n" + + " const std::uint64_t offset,\n" + + " const std::uint64_t bufferLength,\n" + + " const std::uint64_t actingBlockLength,\n" + + " const std::uint64_t actingVersion,\n" + + " CodecState codecState) :\n" + + " m_buffer(buffer),\n" + + " m_bufferLength(bufferLength),\n" + + " m_offset(offset),\n" + + " m_position(sbeCheckPosition(offset + actingBlockLength)),\n" + + " m_actingBlockLength(actingBlockLength),\n" + + " m_actingVersion(actingVersion),\n" + + " m_codecState(codecState)\n" + + " {\n" + + " }\n\n", + className); + return String.format( " %1$s() = default;\n\n" + + "%2$s" + + " %1$s(\n" + " char *buffer,\n" + " const std::uint64_t offset,\n" + @@ -1962,10 +2643,14 @@ private static CharSequence generateConstructorsAndOperators(final String classN " %1$s(buffer, 0, bufferLength, actingBlockLength, actingVersion)\n" + " {\n" + " }\n\n", - className); + className, + constructorWithCodecState); } - private CharSequence generateMessageFlyweightCode(final String className, final Token token) + private CharSequence generateMessageFlyweightCode( + final String className, + final Token token, + final FieldPrecedenceModel fieldPrecedenceModel) { final String blockLengthType = cppTypeName(ir.headerStructure().blockLengthType()); final String templateIdType = cppTypeName(ir.headerStructure().templateIdType()); @@ -1975,20 +2660,28 @@ private CharSequence generateMessageFlyweightCode(final String className, final final String headerType = ir.headerStructure().tokens().get(0).name(); final String semanticVersion = ir.semanticVersion() == null ? "" : ir.semanticVersion(); + + final String codecStateArgument = null == fieldPrecedenceModel ? "" : ", m_codecState"; + return String.format( "private:\n" + + "%15$s" + + "%16$s" + " char *m_buffer = nullptr;\n" + " std::uint64_t m_bufferLength = 0;\n" + " std::uint64_t m_offset = 0;\n" + " std::uint64_t m_position = 0;\n" + " std::uint64_t m_actingBlockLength = 0;\n" + - " std::uint64_t m_actingVersion = 0;\n\n" + + " std::uint64_t m_actingVersion = 0;\n" + + "%17$s" + " inline std::uint64_t *sbePositionPtr() SBE_NOEXCEPT\n" + " {\n" + " return &m_position;\n" + " }\n\n" + + "%22$s" + + "public:\n" + " static const %1$s SBE_BLOCK_LENGTH = %2$s;\n" + " static const %3$s SBE_TEMPLATE_ID = %4$s;\n" + @@ -2015,7 +2708,9 @@ private CharSequence generateMessageFlyweightCode(final String className, final " using messageHeader = %12$s;\n\n" + + "%18$s" + "%11$s" + + " SBE_NODISCARD static SBE_CONSTEXPR %1$s sbeBlockLength() SBE_NOEXCEPT\n" + " {\n" + " return %2$s;\n" + @@ -2058,7 +2753,14 @@ private CharSequence generateMessageFlyweightCode(final String className, final " %10$s &wrapForEncode(char *buffer, const std::uint64_t offset, const std::uint64_t bufferLength)\n" + " {\n" + - " return *this = %10$s(buffer, offset, bufferLength, sbeBlockLength(), sbeSchemaVersion());\n" + + " m_buffer = buffer;\n" + + " m_bufferLength = bufferLength;\n" + + " m_offset = offset;\n" + + " m_actingBlockLength = sbeBlockLength();\n" + + " m_actingVersion = sbeSchemaVersion();\n" + + " m_position = sbeCheckPosition(m_offset + m_actingBlockLength);\n" + + "%19$s" + + " return *this;\n" + " }\n\n" + " %10$s &wrapAndApplyHeader(" + @@ -2072,14 +2774,18 @@ private CharSequence generateMessageFlyweightCode(final String className, final " .schemaId(sbeSchemaId())\n" + " .version(sbeSchemaVersion());\n\n" + - " return *this = %10$s(\n" + - " buffer,\n" + - " offset + messageHeader::encodedLength(),\n" + - " bufferLength,\n" + - " sbeBlockLength(),\n" + - " sbeSchemaVersion());\n" + + " m_buffer = buffer;\n" + + " m_bufferLength = bufferLength;\n" + + " m_offset = offset + messageHeader::encodedLength();\n" + + " m_actingBlockLength = sbeBlockLength();\n" + + " m_actingVersion = sbeSchemaVersion();\n" + + " m_position = sbeCheckPosition(m_offset + m_actingBlockLength);\n" + + "%19$s" + + " return *this;\n" + " }\n\n" + + "%20$s" + + " %10$s &wrapForDecode(\n" + " char *buffer,\n" + " const std::uint64_t offset,\n" + @@ -2087,7 +2793,14 @@ private CharSequence generateMessageFlyweightCode(final String className, final " const std::uint64_t actingVersion,\n" + " const std::uint64_t bufferLength)\n" + " {\n" + - " return *this = %10$s(buffer, offset, bufferLength, actingBlockLength, actingVersion);\n" + + " m_buffer = buffer;\n" + + " m_bufferLength = bufferLength;\n" + + " m_offset = offset;\n" + + " m_actingBlockLength = actingBlockLength;\n" + + " m_actingVersion = actingVersion;\n" + + " m_position = sbeCheckPosition(m_offset + m_actingBlockLength);\n" + + "%21$s" + + " return *this;\n" + " }\n\n" + " %10$s &sbeRewind()\n" + @@ -2123,7 +2836,7 @@ private CharSequence generateMessageFlyweightCode(final String className, final " SBE_NODISCARD std::uint64_t decodeLength() const\n" + " {\n" + - " %10$s skipper(m_buffer, m_offset, m_bufferLength, sbeBlockLength(), m_actingVersion);\n" + + " %10$s skipper(m_buffer, m_offset, m_bufferLength, sbeBlockLength(), m_actingVersion%14$s);\n" + " skipper.skip();\n" + " return skipper.encodedLength();\n" + " }\n\n" + @@ -2157,15 +2870,269 @@ private CharSequence generateMessageFlyweightCode(final String className, final generateLiteral(ir.headerStructure().schemaVersionType(), Integer.toString(ir.version())), semanticType, className, - generateConstructorsAndOperators(className), + generateConstructorsAndOperators(className, fieldPrecedenceModel), formatClassName(headerType), - semanticVersion); + semanticVersion, + codecStateArgument, + generateFieldOrderStateEnum(fieldPrecedenceModel), + generateLookupTableDeclarations(fieldPrecedenceModel), + generateFieldOrderStateMember(fieldPrecedenceModel), + generateAccessOrderErrorType(fieldPrecedenceModel), + generateEncoderWrapListener(fieldPrecedenceModel), + generateDecoderWrapListener(fieldPrecedenceModel), + generateDecoderWrapListenerCall(fieldPrecedenceModel), + generateHiddenCopyConstructor(" ", className)); + } + + private CharSequence generateAccessOrderErrorType(final FieldPrecedenceModel fieldPrecedenceModel) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + sb.append(INDENT).append("class AccessOrderError : public std::logic_error\n") + .append(INDENT).append("{\n") + .append(INDENT).append("public:\n") + .append(INDENT).append(" explicit AccessOrderError(const std::string &msg) : std::logic_error(msg) {}\n") + .append(INDENT).append("};\n\n"); + return sb; + } + + private static CharSequence generateLookupTableDeclarations(final FieldPrecedenceModel fieldPrecedenceModel) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + sb.append(INDENT).append("static const std::string STATE_NAME_LOOKUP[") + .append(fieldPrecedenceModel.stateCount()) + .append("];\n"); + sb.append(INDENT).append("static const std::string STATE_TRANSITIONS_LOOKUP[") + .append(fieldPrecedenceModel.stateCount()) + .append("];\n\n"); + + sb.append(INDENT).append("static std::string codecStateName(CodecState state)\n") + .append(INDENT).append("{\n") + .append(TWO_INDENT).append("return STATE_NAME_LOOKUP[static_cast(state)];\n") + .append(INDENT).append("}\n\n"); + + sb.append(INDENT).append("static std::string codecStateTransitions(CodecState state)\n") + .append(INDENT).append("{\n") + .append(TWO_INDENT).append("return STATE_TRANSITIONS_LOOKUP[static_cast(state)];\n") + .append(INDENT).append("}\n\n"); + + return sb; + } + + private static void generateLookupTableDefinitions( + final StringBuilder sb, + final String className, + final FieldPrecedenceModel fieldPrecedenceModel) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append("\n").append("const std::string ").append(className).append("::STATE_NAME_LOOKUP[") + .append(fieldPrecedenceModel.stateCount()).append("] =\n") + .append("{\n"); + fieldPrecedenceModel.forEachStateOrderedByStateNumber(state -> + { + sb.append(INDENT).append("\"").append(state.name()).append("\",\n"); + }); + sb.append("};\n\n"); + + sb.append("const std::string ").append(className).append("::STATE_TRANSITIONS_LOOKUP[") + .append(fieldPrecedenceModel.stateCount()).append("] =\n") + .append("{\n"); + fieldPrecedenceModel.forEachStateOrderedByStateNumber(state -> + { + sb.append(INDENT).append("\""); + final MutableBoolean isFirst = new MutableBoolean(true); + final Set transitionDescriptions = new HashSet<>(); + fieldPrecedenceModel.forEachTransitionFrom(state, transitionGroup -> + { + if (transitionDescriptions.add(transitionGroup.exampleCode())) + { + if (isFirst.get()) + { + isFirst.set(false); + } + else + { + sb.append(", "); + } + + sb.append("\\\"").append(transitionGroup.exampleCode()).append("\\\""); + } + }); + sb.append("\",\n"); + }); + sb.append("};\n\n"); + } + + private static CharSequence qualifiedStateCase(final FieldPrecedenceModel.State state) + { + return "CodecState::" + state.name(); + } + + private static CharSequence stateCaseForSwitchCase(final FieldPrecedenceModel.State state) + { + return qualifiedStateCase(state); + } + + private static CharSequence unqualifiedStateCase(final FieldPrecedenceModel.State state) + { + return state.name(); + } + + private static CharSequence generateFieldOrderStateEnum(final FieldPrecedenceModel fieldPrecedenceModel) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + + sb.append(" /**\n"); + sb.append(" * The states in which a encoder/decoder/codec can live.\n"); + sb.append(" *\n"); + sb.append(" *

The state machine diagram below, encoded in the dot language, describes\n"); + sb.append(" * the valid state transitions according to the order in which fields may be\n"); + sb.append(" * accessed safely. Tools such as PlantUML and Graphviz can render it.\n"); + sb.append(" *\n"); + sb.append(" *

{@code\n");
+        fieldPrecedenceModel.generateGraph(sb, "     *   ");
+        sb.append("     * }
\n"); + sb.append(" */\n"); + sb.append(INDENT).append("enum class CodecState\n") + .append(INDENT).append("{\n"); + fieldPrecedenceModel.forEachStateOrderedByStateNumber(state -> + { + sb.append(INDENT).append(INDENT).append(unqualifiedStateCase(state)) + .append(" = ").append(state.number()) + .append(",\n"); + }); + sb.append(INDENT).append("};\n\n"); + + return sb; + } + + private static CharSequence generateFieldOrderStateMember(final FieldPrecedenceModel fieldPrecedenceModel) + { + if (null == fieldPrecedenceModel) + { + return "\n"; + } + + final StringBuilder sb = new StringBuilder(); + + sb.append(INDENT).append("CodecState m_codecState = ") + .append(qualifiedStateCase(fieldPrecedenceModel.notWrappedState())) + .append(";\n\n"); + + sb.append(INDENT).append("CodecState codecState() const\n") + .append(INDENT).append("{\n") + .append(INDENT).append(INDENT).append("return m_codecState;\n") + .append(INDENT).append("}\n\n"); + + sb.append(INDENT).append("CodecState *codecStatePtr()\n") + .append(INDENT).append("{\n") + .append(INDENT).append(INDENT).append("return &m_codecState;\n") + .append(INDENT).append("}\n\n"); + + sb.append(INDENT).append("void codecState(CodecState newState)\n") + .append(INDENT).append("{\n") + .append(INDENT).append(INDENT).append("m_codecState = newState;\n") + .append(INDENT).append("}\n\n"); + + return sb; + } + + private static CharSequence generateDecoderWrapListener(final FieldPrecedenceModel fieldPrecedenceModel) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + if (fieldPrecedenceModel.versionCount() == 1) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + sb.append(INDENT).append("void onWrapForDecode(std::uint64_t actingVersion)\n") + .append(INDENT).append("{\n") + .append(INDENT).append(INDENT).append("switch(actingVersion)\n") + .append(INDENT).append(INDENT).append("{\n"); + + fieldPrecedenceModel.forEachWrappedStateByVersion((version, state) -> + { + sb.append(INDENT).append(TWO_INDENT).append("case ").append(version).append(":\n") + .append(INDENT).append(THREE_INDENT).append("codecState(") + .append(qualifiedStateCase(state)).append(");\n") + .append(INDENT).append(THREE_INDENT).append("break;\n"); + }); + + sb.append(INDENT).append(TWO_INDENT).append("default:\n") + .append(INDENT).append(THREE_INDENT).append("codecState(") + .append(qualifiedStateCase(fieldPrecedenceModel.latestVersionWrappedState())).append(");\n") + .append(INDENT).append(THREE_INDENT).append("break;\n") + .append(INDENT).append(INDENT).append("}\n") + .append(INDENT).append("}\n\n"); + + return sb; + } + + + private CharSequence generateDecoderWrapListenerCall(final FieldPrecedenceModel fieldPrecedenceModel) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + if (fieldPrecedenceModel.versionCount() == 1) + { + final StringBuilder sb = new StringBuilder(); + sb.append("#if defined(").append(precedenceChecksFlagName).append(")\n") + .append(TWO_INDENT).append("codecState(") + .append(qualifiedStateCase(fieldPrecedenceModel.latestVersionWrappedState())).append(");\n") + .append("#endif\n"); + return sb; + } + + return generateAccessOrderListenerCall(fieldPrecedenceModel, TWO_INDENT, "onWrapForDecode", "actingVersion"); + } + + private CharSequence generateEncoderWrapListener(final FieldPrecedenceModel fieldPrecedenceModel) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + sb.append("#if defined(").append(precedenceChecksFlagName).append(")\n") + .append(TWO_INDENT).append("codecState(") + .append(qualifiedStateCase(fieldPrecedenceModel.latestVersionWrappedState())) + .append(");\n") + .append("#endif\n"); + return sb; } private void generateFields( final StringBuilder sb, final String containingClassName, final List tokens, + final FieldPrecedenceModel fieldPrecedenceModel, final String indent) { for (int i = 0, size = tokens.size(); i < size; i++) @@ -2179,23 +3146,29 @@ private void generateFields( generateFieldMetaAttributeMethod(sb, signalToken, indent); generateFieldCommonMethods(indent, sb, signalToken, encodingToken, propertyName); + generateAccessOrderListenerMethod(sb, fieldPrecedenceModel, indent, signalToken); + switch (encodingToken.signal()) { case ENCODING: generatePrimitiveProperty( - sb, containingClassName, propertyName, signalToken, encodingToken, indent); + sb, containingClassName, propertyName, signalToken, encodingToken, + fieldPrecedenceModel, indent); break; case BEGIN_ENUM: - generateEnumProperty(sb, containingClassName, signalToken, propertyName, encodingToken, indent); + generateEnumProperty(sb, containingClassName, signalToken, propertyName, encodingToken, + fieldPrecedenceModel, indent); break; case BEGIN_SET: - generateBitsetProperty(sb, propertyName, encodingToken, indent); + generateBitsetProperty(sb, propertyName, signalToken, encodingToken, + fieldPrecedenceModel, indent); break; case BEGIN_COMPOSITE: - generateCompositeProperty(sb, propertyName, encodingToken, indent); + generateCompositeProperty(sb, propertyName, signalToken, encodingToken, + fieldPrecedenceModel, indent); break; default: @@ -2310,6 +3283,7 @@ private void generateEnumProperty( final Token fieldToken, final String propertyName, final Token encodingToken, + final FieldPrecedenceModel fieldPrecedenceModel, final String indent) { final String enumName = formatClassName(encodingToken.applicableTypeName()); @@ -2362,21 +3336,31 @@ private void generateEnumProperty( else { final String offsetStr = Integer.toString(offset); + + final CharSequence accessOrderListenerCall = generateAccessOrderListenerCall( + fieldPrecedenceModel, indent + TWO_INDENT, fieldToken); + + final String noexceptDeclaration = noexceptDeclaration(fieldPrecedenceModel); + new Formatter(sb).format("\n" + - indent + " SBE_NODISCARD %1$s %2$sRaw() const SBE_NOEXCEPT\n" + + indent + " SBE_NODISCARD %1$s %2$sRaw() const%6$s\n" + indent + " {\n" + "%3$s" + "%4$s" + + "%5$s" + indent + " }\n", typeName, propertyName, generateFieldNotPresentCondition(fieldToken.version(), encodingToken.encoding(), indent), - generateLoadValue(primitiveType, offsetStr, encodingToken.encoding().byteOrder(), indent)); + accessOrderListenerCall, + generateLoadValue(primitiveType, offsetStr, encodingToken.encoding().byteOrder(), indent), + noexceptDeclaration); new Formatter(sb).format("\n" + indent + " SBE_NODISCARD %1$s::Value %2$s() const\n" + indent + " {\n" + "%3$s" + + "%7$s" + indent + " %5$s val;\n" + indent + " std::memcpy(&val, m_buffer + m_offset + %6$d, sizeof(%5$s));\n" + indent + " return %1$s::get(%4$s(val));\n" + @@ -2386,11 +3370,13 @@ private void generateEnumProperty( generateEnumFieldNotPresentCondition(fieldToken.version(), enumName, indent), formatByteOrderEncoding(encodingToken.encoding().byteOrder(), primitiveType), typeName, - offset); + offset, + accessOrderListenerCall); new Formatter(sb).format("\n" + - indent + " %1$s &%2$s(const %3$s::Value value) SBE_NOEXCEPT\n" + + indent + " %1$s &%2$s(const %3$s::Value value)%8$s\n" + indent + " {\n" + + "%7$s" + indent + " %4$s val = %6$s(value);\n" + indent + " std::memcpy(m_buffer + m_offset + %5$d, &val, sizeof(%4$s));\n" + indent + " return *this;\n" + @@ -2400,15 +3386,22 @@ private void generateEnumProperty( enumName, typeName, offset, - formatByteOrderEncoding(encodingToken.encoding().byteOrder(), primitiveType)); + formatByteOrderEncoding(encodingToken.encoding().byteOrder(), primitiveType), + accessOrderListenerCall, + noexceptDeclaration); } } - private static void generateBitsetProperty( - final StringBuilder sb, final String propertyName, final Token token, final String indent) + private void generateBitsetProperty( + final StringBuilder sb, + final String propertyName, + final Token fieldToken, + final Token encodingToken, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent) { - final String bitsetName = formatClassName(token.applicableTypeName()); - final int offset = token.offset(); + final String bitsetName = formatClassName(encodingToken.applicableTypeName()); + final int offset = encodingToken.offset(); new Formatter(sb).format("\n" + indent + "private:\n" + @@ -2418,15 +3411,20 @@ private static void generateBitsetProperty( bitsetName, propertyName); + final CharSequence accessOrderListenerCall = generateAccessOrderListenerCall( + fieldPrecedenceModel, indent + TWO_INDENT, fieldToken); + new Formatter(sb).format( indent + " SBE_NODISCARD %1$s &%2$s()\n" + indent + " {\n" + + "%4$s" + indent + " m_%2$s.wrap(m_buffer, m_offset + %3$d, m_actingVersion, m_bufferLength);\n" + indent + " return m_%2$s;\n" + indent + " }\n", bitsetName, propertyName, - offset); + offset, + accessOrderListenerCall); new Formatter(sb).format("\n" + indent + " static SBE_CONSTEXPR std::size_t %1$sEncodingLength() SBE_NOEXCEPT\n" + @@ -2434,13 +3432,18 @@ private static void generateBitsetProperty( indent + " return %2$d;\n" + indent + " }\n", propertyName, - token.encoding().primitiveType().size()); + encodingToken.encoding().primitiveType().size()); } - private static void generateCompositeProperty( - final StringBuilder sb, final String propertyName, final Token token, final String indent) + private void generateCompositeProperty( + final StringBuilder sb, + final String propertyName, + final Token fieldToken, + final Token encodingToken, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent) { - final String compositeName = formatClassName(token.applicableTypeName()); + final String compositeName = formatClassName(encodingToken.applicableTypeName()); new Formatter(sb).format("\n" + "private:\n" + @@ -2450,15 +3453,20 @@ private static void generateCompositeProperty( compositeName, propertyName); + final CharSequence accessOrderListenerCall = generateAccessOrderListenerCall( + fieldPrecedenceModel, indent + TWO_INDENT, fieldToken); + new Formatter(sb).format( indent + " SBE_NODISCARD %1$s &%2$s()\n" + indent + " {\n" + + "%4$s" + indent + " m_%2$s.wrap(m_buffer, m_offset + %3$d, m_actingVersion, m_bufferLength);\n" + indent + " return m_%2$s;\n" + indent + " }\n", compositeName, propertyName, - token.offset()); + encodingToken.offset(), + accessOrderListenerCall); } private CharSequence generateNullValueLiteral(final PrimitiveType primitiveType, final Encoding encoding) diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharp.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharp.java index 5b4870a321..ee55d214d5 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharp.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharp.java @@ -18,6 +18,7 @@ import uk.co.real_logic.sbe.generation.CodeGenerator; import uk.co.real_logic.sbe.generation.TargetCodeGenerator; +import uk.co.real_logic.sbe.generation.TargetCodeGeneratorLoader; import uk.co.real_logic.sbe.ir.Ir; /** @@ -30,6 +31,9 @@ public class CSharp implements TargetCodeGenerator */ public CodeGenerator newInstance(final Ir ir, final String outputDir) { - return new CSharpGenerator(ir, new CSharpNamespaceOutputManager(outputDir, ir.applicableNamespace())); + return new CSharpGenerator( + ir, + TargetCodeGeneratorLoader.precedenceChecks(), + new CSharpNamespaceOutputManager(outputDir, ir.applicableNamespace())); } } diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpGenerator.java index 5a607a8414..8fe17df71a 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpGenerator.java @@ -19,26 +19,30 @@ import uk.co.real_logic.sbe.PrimitiveType; import uk.co.real_logic.sbe.PrimitiveValue; import uk.co.real_logic.sbe.generation.CodeGenerator; -import org.agrona.generation.OutputManager; import uk.co.real_logic.sbe.generation.Generators; -import uk.co.real_logic.sbe.ir.*; +import uk.co.real_logic.sbe.generation.common.FieldPrecedenceModel; +import uk.co.real_logic.sbe.generation.common.PrecedenceChecks; +import uk.co.real_logic.sbe.ir.Encoding; +import uk.co.real_logic.sbe.ir.Ir; +import uk.co.real_logic.sbe.ir.Signal; +import uk.co.real_logic.sbe.ir.Token; import org.agrona.Verify; +import org.agrona.collections.MutableBoolean; +import org.agrona.generation.OutputManager; import java.io.IOException; import java.io.Writer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static java.lang.System.lineSeparator; - import static uk.co.real_logic.sbe.generation.Generators.toLowerFirstChar; import static uk.co.real_logic.sbe.generation.Generators.toUpperFirstChar; import static uk.co.real_logic.sbe.generation.csharp.CSharpUtil.*; -import static uk.co.real_logic.sbe.ir.GenerationUtil.collectVarData; -import static uk.co.real_logic.sbe.ir.GenerationUtil.collectGroups; -import static uk.co.real_logic.sbe.ir.GenerationUtil.collectFields; -import static uk.co.real_logic.sbe.ir.GenerationUtil.findEndSignal; +import static uk.co.real_logic.sbe.ir.GenerationUtil.*; /** * Codec generator for the CSharp programming language. @@ -54,6 +58,8 @@ public class CSharpGenerator implements CodeGenerator private final Ir ir; private final OutputManager outputManager; + private final PrecedenceChecks precedenceChecks; + private final String precedenceChecksFlagName; /** * Create a new C# language {@link CodeGenerator}. @@ -62,11 +68,32 @@ public class CSharpGenerator implements CodeGenerator * @param outputManager for generating the codecs to. */ public CSharpGenerator(final Ir ir, final OutputManager outputManager) + { + this( + ir, + PrecedenceChecks.newInstance(new PrecedenceChecks.Context()), + outputManager + ); + } + + /** + * Create a new C# language {@link CodeGenerator}. + * + * @param ir for the messages and types. + * @param precedenceChecks whether and how to perform field precedence checks. + * @param outputManager for generating the codecs to. + */ + public CSharpGenerator( + final Ir ir, + final PrecedenceChecks precedenceChecks, + final OutputManager outputManager) { Verify.notNull(ir, "ir"); Verify.notNull(outputManager, "outputManager"); this.ir = ir; + this.precedenceChecks = precedenceChecks; + this.precedenceChecksFlagName = precedenceChecks.context().precedenceChecksFlagName(); this.outputManager = outputManager; } @@ -123,32 +150,38 @@ public void generate() throws IOException { final Token msgToken = tokens.get(0); final String className = formatClassName(msgToken.name()); + final String stateClassName = className + ".CodecState"; + final FieldPrecedenceModel fieldPrecedenceModel = precedenceChecks.createCodecModel(stateClassName, tokens); try (Writer out = outputManager.createOutput(className)) { - out.append(generateFileHeader(ir.applicableNamespace())); - out.append(generateDocumentation(BASE_INDENT, msgToken)); - out.append(generateClassDeclaration(className)); - out.append(generateMessageFlyweightCode(className, msgToken, BASE_INDENT)); - final List messageBody = tokens.subList(1, tokens.size() - 1); int offset = 0; - final List fields = new ArrayList<>(); offset = collectFields(messageBody, offset, fields); - out.append(generateFields(fields, BASE_INDENT)); - final List groups = new ArrayList<>(); offset = collectGroups(messageBody, offset, groups); + final List varData = new ArrayList<>(); + collectVarData(messageBody, offset, varData); + + out.append(generateFileHeader(ir.applicableNamespace())); + out.append(generateDocumentation(BASE_INDENT, msgToken)); + out.append(generateClassDeclaration(className)); + out.append(generateMessageFlyweightCode(className, msgToken, fieldPrecedenceModel, BASE_INDENT)); + + out.append(generateFieldOrderStates(BASE_INDENT + INDENT, fieldPrecedenceModel)); + out.append(generateFullyEncodedCheck(BASE_INDENT + INDENT, fieldPrecedenceModel)); + + out.append(generateFields(fieldPrecedenceModel, fields, BASE_INDENT)); + final StringBuilder sb = new StringBuilder(); - generateGroups(sb, className, groups, BASE_INDENT); + generateGroups(sb, className, groups, fieldPrecedenceModel, BASE_INDENT); out.append(sb); - final List varData = new ArrayList<>(); - collectVarData(messageBody, offset, varData); - out.append(generateVarData(varData, BASE_INDENT + INDENT)); + out.append(generateVarData(fieldPrecedenceModel, varData, BASE_INDENT + INDENT)); - out.append(generateDisplay(toUpperFirstChar(msgToken.name()), fields, groups, varData)); + out.append(generateDisplay(toUpperFirstChar(msgToken.name()), + fields, groups, varData, fieldPrecedenceModel)); out.append(INDENT + "}\n"); out.append("}\n"); @@ -160,6 +193,7 @@ private void generateGroups( final StringBuilder sb, final String parentMessageClassName, final List tokens, + final FieldPrecedenceModel fieldPrecedenceModel, final String indent) { for (int i = 0, size = tokens.size(); i < size; i++) @@ -170,23 +204,24 @@ private void generateGroups( throw new IllegalStateException("tokens must begin with BEGIN_GROUP: token=" + groupToken); } final String groupName = groupToken.name(); - sb.append(generateGroupProperty(groupName, groupToken, indent + INDENT)); + sb.append(generateGroupProperty(groupName, fieldPrecedenceModel, groupToken, indent + INDENT)); - generateGroupClassHeader(sb, groupName, parentMessageClassName, tokens, i, indent + INDENT); + generateGroupClassHeader(sb, groupName, parentMessageClassName, tokens, + fieldPrecedenceModel, i, indent + INDENT); i++; i += tokens.get(i).componentTokenCount(); final List fields = new ArrayList<>(); i = collectFields(tokens, i, fields); - sb.append(generateFields(fields, indent + INDENT)); + sb.append(generateFields(fieldPrecedenceModel, fields, indent + INDENT)); final List groups = new ArrayList<>(); i = collectGroups(tokens, i, groups); - generateGroups(sb, parentMessageClassName, groups, indent + INDENT); + generateGroups(sb, parentMessageClassName, groups, fieldPrecedenceModel, indent + INDENT); final List varData = new ArrayList<>(); i = collectVarData(tokens, i, varData); - sb.append(generateVarData(varData, indent + INDENT + INDENT)); + sb.append(generateVarData(fieldPrecedenceModel, varData, indent + INDENT + INDENT)); appendGroupInstanceDisplay(sb, fields, groups, varData, indent + TWO_INDENT); @@ -199,6 +234,7 @@ private void generateGroupClassHeader( final String groupName, final String parentMessageClassName, final List tokens, + final FieldPrecedenceModel fieldPrecedenceModel, final int index, final String indent) { @@ -232,6 +268,16 @@ private void generateGroupClassHeader( groupName)); } + sb.append("\n") + // The "file" access modifier is more suitable but only available from C# 11 onwards. + .append(indent).append(INDENT).append("internal void NotPresent()\n") + .append(indent).append(INDENT).append("{\n") + .append(indent).append(TWO_INDENT).append("_count = 0;\n") + .append(indent).append(TWO_INDENT).append("_index = 0;\n") + .append(indent).append(TWO_INDENT).append("_buffer = null;\n") + .append(indent).append(TWO_INDENT).append("_offset = 0;\n") + .append(indent).append(INDENT).append("}\n"); + sb.append(String.format("\n" + indent + INDENT + "public void WrapForDecode(%s parentMessage, DirectBuffer buffer, int actingVersion)\n" + indent + INDENT + "{\n" + @@ -284,27 +330,50 @@ private void generateGroupClassHeader( blockLength, dimensionHeaderLength)); - generateGroupEnumerator(sb, groupName, typeForNumInGroup, indent); + if (null != fieldPrecedenceModel) + { + sb.append("\n") + .append(indent).append(" private CodecState codecState()\n") + .append(indent).append(" {\n") + .append(indent).append(" return _parentMessage.codecState();\n") + .append(indent).append(" }\n"); + + sb.append("\n") + .append(indent).append(" private void codecState(CodecState newState)\n") + .append(indent).append(" {\n") + .append(indent).append(" _parentMessage.codecState(newState);\n") + .append(indent).append(" }\n"); + } + + final Token groupToken = tokens.get(index); + generateGroupEnumerator(sb, fieldPrecedenceModel, groupToken, groupName, typeForNumInGroup, indent); } private void generateGroupEnumerator( final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final Token groupToken, final String groupName, final String typeForNumInGroup, final String indent) { + generateAccessOrderListenerMethodForNextGroupElement(sb, fieldPrecedenceModel, indent + INDENT, groupToken); + generateAccessOrderListenerMethodForResetGroupCount(sb, fieldPrecedenceModel, indent + INDENT, groupToken); + sb.append( indent + INDENT + "public int ActingBlockLength { get { return _blockLength; } }\n\n" + indent + INDENT + "public int Count { get { return _count; } }\n\n" + - indent + INDENT + "public bool HasNext { get { return _index < _count; } }\n"); + indent + INDENT + "public bool HasNext { get { return _index < _count; } }\n\n"); sb.append(String.format("\n" + indent + INDENT + "public int ResetCountToIndex()\n" + indent + INDENT + "{\n" + + "%s" + indent + INDENT + INDENT + "_count = _index;\n" + indent + INDENT + INDENT + "_dimensions.NumInGroup = (%s) _count;\n\n" + indent + INDENT + INDENT + "return _count;\n" + indent + INDENT + "}\n", + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + TWO_INDENT, "OnResetCountToIndex"), typeForNumInGroup)); sb.append(String.format("\n" + @@ -314,6 +383,7 @@ private void generateGroupEnumerator( indent + INDENT + INDENT + "{\n" + indent + INDENT + INDENT + INDENT + "ThrowHelper.ThrowInvalidOperationException();\n" + indent + INDENT + INDENT + "}\n\n" + + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + TWO_INDENT, "OnNextElementAccessed") + indent + INDENT + INDENT + "_offset = _parentMessage.Limit;\n" + indent + INDENT + INDENT + "_parentMessage.Limit = _offset + _blockLength;\n" + indent + INDENT + INDENT + "++_index;\n\n" + @@ -338,7 +408,11 @@ private boolean isRepresentableByInt32(final Encoding encoding) encoding.applicableMaxValue().longValue() <= Integer.MAX_VALUE; } - private CharSequence generateGroupProperty(final String groupName, final Token token, final String indent) + private CharSequence generateGroupProperty( + final String groupName, + final FieldPrecedenceModel fieldPrecedenceModel, + final Token token, + final String indent) { final StringBuilder sb = new StringBuilder(); @@ -355,37 +429,65 @@ private CharSequence generateGroupProperty(final String groupName, final Token t toUpperFirstChar(groupName), token.id())); + generateAccessOrderListenerMethodForGroupWrap(sb, fieldPrecedenceModel, indent, token); + generateSinceActingDeprecated(sb, indent, toUpperFirstChar(groupName), token); + final String groupField = "_" + toLowerFirstChar(groupName); + + final CharSequence accessOrderListenerCallOnDecode = generateAccessOrderListenerCall( + fieldPrecedenceModel, + indent + TWO_INDENT, + token, + groupField + ".Count", + "\"decode\""); + sb.append(String.format("\n" + "%1$s" + indent + "public %2$sGroup %3$s\n" + indent + "{\n" + indent + INDENT + "get\n" + indent + INDENT + "{\n" + + "%5$s" + + indent + INDENT + INDENT + "_%4$s.WrapForDecode(_parentMessage, _buffer, _actingVersion);\n" + + "%6$s" + + indent + INDENT + INDENT + "return _%4$s;\n" + indent + INDENT + "}\n" + indent + "}\n", generateDocumentation(indent, token), className, toUpperFirstChar(groupName), - toLowerFirstChar(groupName))); - + toLowerFirstChar(groupName), + generateGroupNotPresentCondition(token.version(), indent + INDENT + INDENT, groupField), + accessOrderListenerCallOnDecode)); + + final CharSequence accessOrderListenerCallOnEncode = generateAccessOrderListenerCall( + fieldPrecedenceModel, + indent + INDENT, + token, + "count", + "\"encode\""); sb.append(String.format("\n" + indent + "public %1$sGroup %2$sCount(int count)\n" + indent + "{\n" + + "%4$s" + indent + INDENT + "_%3$s.WrapForEncode(_parentMessage, _buffer, count);\n" + indent + INDENT + "return _%3$s;\n" + indent + "}\n", className, toUpperFirstChar(groupName), - toLowerFirstChar(groupName))); + toLowerFirstChar(groupName), + accessOrderListenerCallOnEncode)); return sb; } - private CharSequence generateVarData(final List tokens, final String indent) + private CharSequence generateVarData( + final FieldPrecedenceModel fieldPrecedenceModel, + final List tokens, + final String indent) { final StringBuilder sb = new StringBuilder(); @@ -412,21 +514,34 @@ private CharSequence generateVarData(final List tokens, final String inde final ByteOrder byteOrder = lengthEncoding.byteOrder(); final String byteOrderStr = generateByteOrder(byteOrder, lengthEncoding.primitiveType().size()); + generateAccessOrderListenerMethodForVarDataLength(sb, fieldPrecedenceModel, indent, token); + generateAccessOrderListenerMethod(sb, fieldPrecedenceModel, indent, token); + sb.append(String.format("\n" + indent + "public const int %sHeaderSize = %d;\n", propertyName, sizeOfLengthField)); + final CharSequence lengthAccessOrderListenerCall = generateAccessOrderListenerCall( + fieldPrecedenceModel, indent + INDENT, accessOrderListenerMethodName(token, "Length")); + sb.append(String.format(indent + "\n" + indent + "public int %1$sLength()\n" + indent + "{\n" + + "%5$s" + + "%6$s" + indent + INDENT + "_buffer.CheckLimit(_parentMessage.Limit + %2$d);\n" + indent + INDENT + "return (int)_buffer.%3$sGet%4$s(_parentMessage.Limit);\n" + indent + "}\n", propertyName, sizeOfLengthField, lengthTypePrefix, - byteOrderStr)); + byteOrderStr, + generateArrayFieldNotPresentCondition(token.version(), indent, "0"), + lengthAccessOrderListenerCall)); + + final CharSequence accessOrderListenerCall = + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + INDENT, token); sb.append(String.format("\n" + indent + "public int Get%1$s(byte[] dst, int dstOffset, int length) =>\n" + @@ -437,6 +552,7 @@ private CharSequence generateVarData(final List tokens, final String inde indent + "public int Get%1$s(Span dst)\n" + indent + "{\n" + "%2$s" + + "%6$s" + indent + INDENT + "const int sizeOfLengthField = %3$d;\n" + indent + INDENT + "int limit = _parentMessage.Limit;\n" + indent + INDENT + "_buffer.CheckLimit(limit + sizeOfLengthField);\n" + @@ -447,15 +563,18 @@ private CharSequence generateVarData(final List tokens, final String inde indent + INDENT + "return bytesCopied;\n" + indent + "}\n", propertyName, - generateArrayFieldNotPresentCondition(token.version(), indent), + generateArrayFieldNotPresentCondition(token.version(), indent, "0"), sizeOfLengthField, lengthTypePrefix, - byteOrderStr)); + byteOrderStr, + accessOrderListenerCall)); sb.append(String.format(indent + "\n" + indent + "// Allocates and returns a new byte array\n" + indent + "public byte[] Get%1$sBytes()\n" + indent + "{\n" + + "%5$s" + + "%6$s" + indent + INDENT + "const int sizeOfLengthField = %2$d;\n" + indent + INDENT + "int limit = _parentMessage.Limit;\n" + indent + INDENT + "_buffer.CheckLimit(limit + sizeOfLengthField);\n" + @@ -468,7 +587,9 @@ private CharSequence generateVarData(final List tokens, final String inde propertyName, sizeOfLengthField, lengthTypePrefix, - byteOrderStr)); + byteOrderStr, + generateArrayFieldNotPresentCondition(token.version(), indent, "new byte[0]"), + accessOrderListenerCall)); sb.append(String.format("\n" + indent + "public int Set%1$s(byte[] src, int srcOffset, int length) =>\n" + @@ -478,6 +599,7 @@ private CharSequence generateVarData(final List tokens, final String inde sb.append(String.format("\n" + indent + "public int Set%1$s(ReadOnlySpan src)\n" + indent + "{\n" + + "%6$s" + indent + INDENT + "const int sizeOfLengthField = %2$d;\n" + indent + INDENT + "int limit = _parentMessage.Limit;\n" + indent + INDENT + "_parentMessage.Limit = limit + sizeOfLengthField + src.Length;\n" + @@ -489,7 +611,8 @@ private CharSequence generateVarData(final List tokens, final String inde sizeOfLengthField, lengthTypePrefix, lengthCSharpType, - byteOrderStr)); + byteOrderStr, + accessOrderListenerCall)); if (characterEncoding != null) // only generate these string based methods if there is an encoding { @@ -497,6 +620,8 @@ private CharSequence generateVarData(final List tokens, final String inde .append(String.format( indent + "public string Get%1$s()\n" + indent + "{\n" + + "%6$s" + + "%7$s" + indent + INDENT + "const int sizeOfLengthField = %2$d;\n" + indent + INDENT + "int limit = _parentMessage.Limit;\n" + indent + INDENT + "_buffer.CheckLimit(limit + sizeOfLengthField);\n" + @@ -507,15 +632,22 @@ private CharSequence generateVarData(final List tokens, final String inde indent + "}\n\n" + indent + "public void Set%1$s(string value)\n" + indent + "{\n" + + "%7$s" + indent + INDENT + "var encoding = %1$sResolvedCharacterEncoding;\n" + indent + INDENT + "const int sizeOfLengthField = %2$d;\n" + indent + INDENT + "int limit = _parentMessage.Limit;\n" + indent + INDENT + "int byteCount = _buffer.SetBytesFromString(encoding, value, " + "limit + sizeOfLengthField);\n" + indent + INDENT + "_parentMessage.Limit = limit + sizeOfLengthField + byteCount;\n" + - indent + INDENT + "_buffer.%3$sPut%4$s(limit, (ushort)byteCount);\n" + + indent + INDENT + "_buffer.%3$sPut%4$s(limit, (%5$s)byteCount);\n" + indent + "}\n", - propertyName, sizeOfLengthField, lengthTypePrefix, byteOrderStr)); + propertyName, + sizeOfLengthField, + lengthTypePrefix, + byteOrderStr, + lengthCSharpType, + generateArrayFieldNotPresentCondition(token.version(), indent, "\"\""), + accessOrderListenerCall)); } } } @@ -588,23 +720,24 @@ private CharSequence generateCompositePropertyElements(final List tokens, { final Token token = tokens.get(i); final String propertyName = formatPropertyName(token.name()); + final FieldPrecedenceModel fieldPrecedenceModel = null; switch (token.signal()) { case ENCODING: - sb.append(generatePrimitiveProperty(propertyName, token, token, indent)); + sb.append(generatePrimitiveProperty(propertyName, token, token, fieldPrecedenceModel, indent)); break; case BEGIN_ENUM: - sb.append(generateEnumProperty(propertyName, token, token, indent)); + sb.append(generateEnumProperty(propertyName, token, token, fieldPrecedenceModel, indent)); break; case BEGIN_SET: - sb.append(generateBitSetProperty(propertyName, token, token, indent)); + sb.append(generateBitSetProperty(propertyName, token, token, fieldPrecedenceModel, indent)); break; case BEGIN_COMPOSITE: - sb.append(generateCompositeProperty(propertyName, token, token, indent)); + sb.append(generateCompositeProperty(propertyName, token, token, fieldPrecedenceModel, indent)); break; default: @@ -747,7 +880,11 @@ private CharSequence generateEnumDeclaration( } private CharSequence generatePrimitiveProperty( - final String propertyName, final Token fieldToken, final Token typeToken, final String indent) + final String propertyName, + final Token fieldToken, + final Token typeToken, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent) { final StringBuilder sb = new StringBuilder(); @@ -759,7 +896,8 @@ private CharSequence generatePrimitiveProperty( } else { - sb.append(generatePrimitivePropertyMethods(propertyName, fieldToken, typeToken, indent)); + sb.append(generatePrimitivePropertyMethods(propertyName, fieldToken, typeToken, + fieldPrecedenceModel, indent)); } return sb; @@ -769,17 +907,20 @@ private CharSequence generatePrimitivePropertyMethods( final String propertyName, final Token fieldToken, final Token typeToken, + final FieldPrecedenceModel fieldPrecedenceModel, final String indent) { final int arrayLength = typeToken.arrayLength(); if (arrayLength == 1) { - return generateSingleValueProperty(propertyName, fieldToken, typeToken, indent + INDENT); + return generateSingleValueProperty(propertyName, fieldToken, typeToken, + fieldPrecedenceModel, indent + INDENT); } else if (arrayLength > 1) { - return generateArrayProperty(propertyName, fieldToken, typeToken, indent + INDENT); + return generateArrayProperty(propertyName, fieldToken, typeToken, + fieldPrecedenceModel, indent + INDENT); } return ""; @@ -809,6 +950,7 @@ private CharSequence generateSingleValueProperty( final String propertyName, final Token fieldToken, final Token typeToken, + final FieldPrecedenceModel fieldPrecedenceModel, final String indent) { final String typeName = cSharpTypeName(typeToken.encoding().primitiveType()); @@ -817,6 +959,9 @@ private CharSequence generateSingleValueProperty( final ByteOrder byteOrder = typeToken.encoding().byteOrder(); final String byteOrderStr = generateByteOrder(byteOrder, typeToken.encoding().primitiveType().size()); + final CharSequence accessOrderListenerCall = + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + TWO_INDENT, fieldToken); + return String.format("\n" + "%1$s" + indent + "public %2$s %3$s\n" + @@ -824,10 +969,12 @@ private CharSequence generateSingleValueProperty( indent + INDENT + "get\n" + indent + INDENT + "{\n" + "%4$s" + + "%8$s" + indent + INDENT + INDENT + "return _buffer.%5$sGet%7$s(_offset + %6$d);\n" + indent + INDENT + "}\n" + indent + INDENT + "set\n" + indent + INDENT + "{\n" + + "%8$s" + indent + INDENT + INDENT + "_buffer.%5$sPut%7$s(_offset + %6$d, value);\n" + indent + INDENT + "}\n" + indent + "}\n\n", @@ -837,7 +984,8 @@ private CharSequence generateSingleValueProperty( generateFieldNotPresentCondition(fieldToken.version(), typeToken.encoding(), indent), typePrefix, offset, - byteOrderStr); + byteOrderStr, + accessOrderListenerCall); } private CharSequence generateFieldNotPresentCondition( @@ -866,16 +1014,34 @@ private CharSequence generateFieldNotPresentCondition( literal); } - private CharSequence generateArrayFieldNotPresentCondition(final int sinceVersion, final String indent) + private CharSequence generateGroupNotPresentCondition( + final int sinceVersion, + final String indent, + final String groupInstanceField) { if (0 == sinceVersion) { return ""; } - return String.format( - indent + INDENT + INDENT + "if (_actingVersion < %d) return 0;\n\n", - sinceVersion); + return indent + "if (_actingVersion < " + sinceVersion + ")" + + indent + "{\n" + + indent + INDENT + groupInstanceField + ".NotPresent();\n" + + indent + INDENT + "return " + groupInstanceField + ";\n" + + indent + "}\n\n"; + } + + private CharSequence generateArrayFieldNotPresentCondition( + final int sinceVersion, + final String indent, + final String defaultValue) + { + if (0 == sinceVersion) + { + return ""; + } + + return indent + INDENT + "if (_actingVersion < " + sinceVersion + ") return " + defaultValue + ";\n\n"; } private CharSequence generateBitSetNotPresentCondition( @@ -912,7 +1078,7 @@ private CharSequence generateArrayProperty( final String propertyName, final Token fieldToken, final Token typeToken, - final String indent) + final FieldPrecedenceModel fieldPrecedenceModel, final String indent) { final String typeName = cSharpTypeName(typeToken.encoding().primitiveType()); final String typePrefix = toUpperFirstChar(typeToken.encoding().primitiveType().primitiveName()); @@ -923,6 +1089,11 @@ private CharSequence generateArrayProperty( final int typeSize = typeToken.encoding().primitiveType().size(); final String propName = toUpperFirstChar(propertyName); + final CharSequence accessOrderListenerCall = + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + INDENT, fieldToken); + final CharSequence accessOrderListenerCallDoubleIndent = + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + TWO_INDENT, fieldToken); + final StringBuilder sb = new StringBuilder(); sb.append(String.format("\n" + @@ -938,12 +1109,14 @@ private CharSequence generateArrayProperty( indent + INDENT + INDENT + "ThrowHelper.ThrowIndexOutOfRangeException(index);\n" + indent + INDENT + "}\n\n" + "%5$s" + + "%10$s" + indent + INDENT + "return _buffer.%6$sGet%9$s(_offset + %7$d + (index * %8$d));\n" + indent + "}\n", generateDocumentation(indent, fieldToken), typeName, propName, fieldLength, generateFieldNotPresentCondition(fieldToken.version(), typeToken.encoding(), indent), - typePrefix, offset, typeSize, byteOrderStr)); + typePrefix, offset, typeSize, byteOrderStr, + accessOrderListenerCall)); sb.append(String.format("\n" + "%1$s" + @@ -953,29 +1126,48 @@ private CharSequence generateArrayProperty( indent + INDENT + "{\n" + indent + INDENT + INDENT + "ThrowHelper.ThrowIndexOutOfRangeException(index);\n" + indent + INDENT + "}\n\n" + + "%9$s" + indent + INDENT + "_buffer.%5$sPut%8$s(_offset + %6$d + (index * %7$d), value);\n" + indent + "}\n", generateDocumentation(indent, fieldToken), - propName, typeName, fieldLength, typePrefix, offset, typeSize, byteOrderStr)); + propName, typeName, fieldLength, typePrefix, offset, typeSize, byteOrderStr, + accessOrderListenerCall)); sb.append(String.format("\n" + "%1$s" + indent + "public ReadOnlySpan<%2$s> %3$s\n" + indent + "{\n" + - indent + INDENT + "get => _buffer.AsReadOnlySpan<%2$s>(_offset + %4$s, %3$sLength);\n" + - indent + INDENT + "set => value.CopyTo(_buffer.AsSpan<%2$s>(_offset + %4$s, %3$sLength));\n" + + indent + INDENT + "get\n" + + indent + INDENT + "{\n" + + "%5$s" + + "%6$s" + + indent + INDENT + INDENT + "return _buffer.AsReadOnlySpan<%2$s>(_offset + %4$s, %3$sLength);\n" + + indent + INDENT + "}\n" + + indent + INDENT + "set\n" + + indent + INDENT + "{\n" + + "%6$s" + + indent + INDENT + INDENT + "value.CopyTo(_buffer.AsSpan<%2$s>(_offset + %4$s, %3$sLength));\n" + + indent + INDENT + "}\n" + indent + "}\n", generateDocumentation(indent, fieldToken), - typeName, propName, offset)); + typeName, propName, offset, + generateArrayFieldNotPresentCondition(fieldToken.version(), + indent + INDENT + INDENT, "new " + typeName + "[0]"), + accessOrderListenerCallDoubleIndent)); sb.append(String.format("\n" + "%1$s" + indent + "public Span<%2$s> %3$sAsSpan()\n" + indent + "{\n" + + "%5$s" + + "%6$s" + indent + INDENT + "return _buffer.AsSpan<%2$s>(_offset + %4$s, %3$sLength);\n" + indent + "}\n", generateDocumentation(indent, fieldToken), - typeName, propName, offset)); + typeName, propName, offset, + generateArrayFieldNotPresentCondition(fieldToken.version(), + indent + INDENT + INDENT, "new " + typeName + "[0]"), + accessOrderListenerCall)); if (typeToken.encoding().primitiveType() == PrimitiveType.CHAR) { @@ -986,9 +1178,13 @@ private CharSequence generateArrayProperty( indent + "{\n" + indent + INDENT + "const int length = %2$d;\n" + "%3$s" + + "%4$s" + indent + INDENT + "return Get%1$s(new Span(dst, dstOffset, length));\n" + indent + "}\n", - propName, fieldLength, generateArrayFieldNotPresentCondition(fieldToken.version(), indent), offset)); + propName, + fieldLength, + generateArrayFieldNotPresentCondition(fieldToken.version(), indent, "0"), + accessOrderListenerCall)); sb.append(String.format("\n" + indent + "public int Get%1$s(Span dst)\n" + @@ -999,10 +1195,15 @@ private CharSequence generateArrayProperty( indent + INDENT + INDENT + "ThrowHelper.ThrowWhenSpanLengthTooSmall(dst.Length);\n" + indent + INDENT + "}\n\n" + "%3$s" + + "%5$s" + indent + INDENT + "_buffer.GetBytes(_offset + %4$d, dst);\n" + indent + INDENT + "return length;\n" + indent + "}\n", - propName, fieldLength, generateArrayFieldNotPresentCondition(fieldToken.version(), indent), offset)); + propName, + fieldLength, + generateArrayFieldNotPresentCondition(fieldToken.version(), indent, "0"), + offset, + accessOrderListenerCall)); sb.append(String.format("\n" + indent + "public void Set%1$s(byte[] src, int srcOffset)\n" + @@ -1019,22 +1220,28 @@ private CharSequence generateArrayProperty( indent + INDENT + "{\n" + indent + INDENT + INDENT + "ThrowHelper.ThrowWhenSpanLengthTooLarge(src.Length);\n" + indent + INDENT + "}\n\n" + + "%4$s" + indent + INDENT + "_buffer.SetBytes(_offset + %3$d, src);\n" + indent + "}\n", - propName, fieldLength, offset)); + propName, fieldLength, offset, + accessOrderListenerCall)); sb.append(String.format("\n" + indent + "public void Set%1$s(string value)\n" + indent + "{\n" + + "%3$s" + indent + INDENT + "_buffer.SetNullTerminatedBytesFromString(%1$sResolvedCharacterEncoding, " + "value, _offset + %2$s, %1$sLength, %1$sNullValue);\n" + indent + "}\n" + indent + "public string Get%1$s()\n" + indent + "{\n" + + "%3$s" + indent + INDENT + "return _buffer.GetStringFromNullTerminatedBytes(%1$sResolvedCharacterEncoding, " + "_offset + %2$s, %1$sLength, %1$sNullValue);\n" + indent + "}\n", - propName, offset)); + propName, + offset, + accessOrderListenerCall)); } return sb; @@ -1156,7 +1363,11 @@ private CharSequence generateFixedFlyweightCode(final int size) size); } - private CharSequence generateMessageFlyweightCode(final String className, final Token token, final String indent) + private CharSequence generateMessageFlyweightCode( + final String className, + final Token token, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent) { final String blockLengthType = cSharpTypeName(ir.headerStructure().blockLengthType()); final String templateIdType = cSharpTypeName(ir.headerStructure().templateIdType()); @@ -1184,16 +1395,18 @@ private CharSequence generateMessageFlyweightCode(final String className, final indent + INDENT + "{\n" + indent + INDENT + INDENT + "_parentMessage = this;\n" + indent + INDENT + "}\n\n" + - indent + INDENT + "public void WrapForEncode(DirectBuffer buffer, int offset)\n" + + indent + INDENT + "public %10$s WrapForEncode(DirectBuffer buffer, int offset)\n" + indent + INDENT + "{\n" + + "%12$s" + indent + INDENT + INDENT + "_buffer = buffer;\n" + indent + INDENT + INDENT + "_offset = offset;\n" + indent + INDENT + INDENT + "_actingBlockLength = BlockLength;\n" + indent + INDENT + INDENT + "_actingVersion = SchemaVersion;\n" + indent + INDENT + INDENT + "Limit = offset + _actingBlockLength;\n" + + indent + INDENT + INDENT + "return this;\n" + indent + INDENT + "}\n\n" + - indent + INDENT + "public void WrapForEncodeAndApplyHeader(DirectBuffer buffer, int offset, " + - " MessageHeader headerEncoder)\n" + + indent + INDENT + "public %10$s WrapForEncodeAndApplyHeader(DirectBuffer buffer, int offset, " + + "MessageHeader headerEncoder)\n" + indent + INDENT + "{\n" + indent + INDENT + INDENT + "headerEncoder.Wrap(buffer, offset, SchemaVersion);\n" + indent + INDENT + INDENT + "headerEncoder.BlockLength = BlockLength;\n" + @@ -1201,16 +1414,27 @@ private CharSequence generateMessageFlyweightCode(final String className, final indent + INDENT + INDENT + "headerEncoder.SchemaId = SchemaId;\n" + indent + INDENT + INDENT + "headerEncoder.Version = SchemaVersion;\n" + indent + INDENT + INDENT + "\n" + - indent + INDENT + INDENT + "WrapForEncode(buffer, offset + MessageHeader.Size);\n" + + indent + INDENT + INDENT + "return WrapForEncode(buffer, offset + MessageHeader.Size);\n" + indent + INDENT + "}\n\n" + - indent + INDENT + "public void WrapForDecode(DirectBuffer buffer, int offset, " + + "%13$s" + + indent + INDENT + "public %10$s WrapForDecode(DirectBuffer buffer, int offset, " + "int actingBlockLength, int actingVersion)\n" + indent + INDENT + "{\n" + + "%14$s" + indent + INDENT + INDENT + "_buffer = buffer;\n" + indent + INDENT + INDENT + "_offset = offset;\n" + indent + INDENT + INDENT + "_actingBlockLength = actingBlockLength;\n" + indent + INDENT + INDENT + "_actingVersion = actingVersion;\n" + indent + INDENT + INDENT + "Limit = offset + _actingBlockLength;\n" + + indent + INDENT + INDENT + "return this;\n" + + indent + INDENT + "}\n\n" + + indent + INDENT + "public %10$s WrapForDecodeAndApplyHeader(DirectBuffer buffer, int offset, " + + "MessageHeader headerDecoder)\n" + + indent + INDENT + "{\n" + + indent + INDENT + INDENT + "headerDecoder.Wrap(buffer, offset, SchemaVersion);\n" + + indent + INDENT + INDENT + "\n" + + indent + INDENT + INDENT + "return WrapForDecode(buffer, offset + MessageHeader.Size, " + + " headerDecoder.BlockLength, headerDecoder.Version);\n" + indent + INDENT + "}\n\n" + indent + INDENT + "public int Size\n" + indent + INDENT + "{\n" + @@ -1241,10 +1465,507 @@ private CharSequence generateMessageFlyweightCode(final String className, final generateLiteral(ir.headerStructure().schemaVersionType(), Integer.toString(ir.version())), semanticType, className, - semanticVersion); + semanticVersion, + generateEncoderWrapListener(fieldPrecedenceModel, indent + TWO_INDENT), + generateDecoderWrapListener(fieldPrecedenceModel, indent + INDENT), + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + TWO_INDENT, + "OnWrapForDecode", "actingVersion")); + } + + private static CharSequence qualifiedStateCase(final FieldPrecedenceModel.State state) + { + return "CodecState." + state.name(); + } + + private static CharSequence stateCaseForSwitchCase(final FieldPrecedenceModel.State state) + { + return qualifiedStateCase(state); + } + + private static CharSequence unqualifiedStateCase(final FieldPrecedenceModel.State state) + { + return state.name(); + } + + private static CharSequence generateFieldOrderStates( + final String indent, + final FieldPrecedenceModel fieldPrecedenceModel) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + + sb.append(indent).append("///\n"); + + sb.append(indent).append("/// \n"); + sb.append(indent).append("/// \n"); + sb.append(indent).append("/// The states in which a encoder/decoder/codec can live.\n"); + sb.append(indent).append("/// \n"); + sb.append(indent).append("/// \n"); + sb.append(indent).append("/// The state machine diagram below, encoded in the dot language, describes\n"); + sb.append(indent).append("/// the valid state transitions according to the order in which fields may be\n"); + sb.append(indent).append("/// accessed safely. Tools such as PlantUML and Graphviz can render it.\n"); + sb.append(indent).append("/// \n"); + sb.append(indent).append("/// \n"); + fieldPrecedenceModel.generateGraph(sb, indent + "/// "); + sb.append(indent).append("/// \n"); + sb.append(indent).append("/// \n"); + sb.append(indent).append("private enum CodecState\n") + .append(indent).append("{\n"); + fieldPrecedenceModel.forEachStateOrderedByStateNumber(state -> + { + sb.append(indent).append(INDENT).append(unqualifiedStateCase(state)) + .append(" = ").append(state.number()) + .append(",\n"); + }); + sb.append(indent).append("}\n\n"); + + sb.append("\n").append(indent).append("private static readonly string[] StateNameLookup = new []\n") + .append(indent).append("{\n"); + fieldPrecedenceModel.forEachStateOrderedByStateNumber(state -> + { + sb.append(indent).append(INDENT).append("\"").append(state.name()).append("\",\n"); + }); + sb.append(indent).append("};\n\n"); + + sb.append(indent).append("private static readonly string[] StateTransitionsLookup = new []\n") + .append(indent).append("{\n"); + fieldPrecedenceModel.forEachStateOrderedByStateNumber(state -> + { + sb.append(indent).append(INDENT).append("\""); + final MutableBoolean isFirst = new MutableBoolean(true); + final Set transitionDescriptions = new HashSet<>(); + fieldPrecedenceModel.forEachTransitionFrom(state, transitionGroup -> + { + if (transitionDescriptions.add(transitionGroup.exampleCode())) + { + if (isFirst.get()) + { + isFirst.set(false); + } + else + { + sb.append(", "); + } + + sb.append("\\\"").append(transitionGroup.exampleCode()).append("\\\""); + } + }); + sb.append("\",\n"); + }); + sb.append(indent).append("};\n\n"); + + sb.append(indent).append("private static string codecStateName(CodecState state)\n") + .append(indent).append("{\n") + .append(indent).append(INDENT).append("return StateNameLookup[(int) state];\n") + .append(indent).append("}\n\n"); + + sb.append(indent).append("private static string codecStateTransitions(CodecState state)\n") + .append(indent).append("{\n") + .append(indent).append(INDENT).append("return StateTransitionsLookup[(int) state];\n") + .append(indent).append("}\n\n"); + + sb.append(indent).append("private CodecState _codecState = ") + .append(qualifiedStateCase(fieldPrecedenceModel.notWrappedState())) + .append(";\n\n"); + + sb.append(indent).append("private CodecState codecState()\n") + .append(indent).append("{\n") + .append(indent).append(INDENT).append("return _codecState;\n") + .append(indent).append("}\n\n"); + + sb.append(indent).append("private void codecState(CodecState newState)\n") + .append(indent).append("{\n") + .append(indent).append(INDENT).append("_codecState = newState;\n") + .append(indent).append("}\n"); + + return sb; + } + + private CharSequence generateFullyEncodedCheck( + final String indent, + final FieldPrecedenceModel fieldPrecedenceModel) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + sb.append("\n"); + + sb.append(indent).append("public void CheckEncodingIsComplete()\n") + .append(indent).append("{\n") + .append("#if ").append(precedenceChecksFlagName).append("\n") + .append(indent).append(INDENT).append("switch (_codecState)\n") + .append(indent).append(INDENT).append("{\n"); + + fieldPrecedenceModel.forEachTerminalEncoderState(state -> + { + sb.append(indent).append(TWO_INDENT).append("case ").append(stateCaseForSwitchCase(state)).append(":\n") + .append(indent).append(THREE_INDENT).append("return;\n"); + }); + + sb.append(indent).append(TWO_INDENT).append("default:\n") + .append(indent).append(THREE_INDENT) + .append("throw new InvalidOperationException(\"Not fully encoded, current state: \" +\n") + .append(indent).append(THREE_INDENT) + .append(INDENT).append("codecStateName(_codecState) + \", allowed transitions: \" +\n") + .append(indent).append(THREE_INDENT) + .append(INDENT).append("codecStateTransitions(_codecState));\n") + .append(indent).append(INDENT).append("}\n") + .append("#endif\n") + .append(indent).append("}\n\n"); + + return sb; + } + + private static String accessOrderListenerMethodName(final Token token) + { + return "On" + Generators.toUpperFirstChar(token.name()) + "Accessed"; } - private CharSequence generateFields(final List tokens, final String indent) + private static String accessOrderListenerMethodName(final Token token, final String suffix) + { + return "On" + Generators.toUpperFirstChar(token.name()) + suffix + "Accessed"; + } + + private static void generateAccessOrderListenerMethod( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append("\n") + .append(indent).append("private void ").append(accessOrderListenerMethodName(token)).append("()\n") + .append(indent).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction fieldAccess = + fieldPrecedenceModel.interactionFactory().accessField(token); + + generateAccessOrderListener( + sb, + indent + INDENT, + "access field", + fieldPrecedenceModel, + fieldAccess); + + sb.append(indent).append("}\n"); + } + + private CharSequence generateAccessOrderListenerCall( + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token, + final String... arguments) + { + return generateAccessOrderListenerCall( + fieldPrecedenceModel, + indent, + accessOrderListenerMethodName(token), + arguments); + } + + private CharSequence generateAccessOrderListenerCall( + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final String methodName, + final String... arguments) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + sb.append("#if ").append(precedenceChecksFlagName).append("\n") + .append(indent).append(methodName).append("("); + + for (int i = 0; i < arguments.length; i++) + { + if (i > 0) + { + sb.append(", "); + } + sb.append(arguments[i]); + } + sb.append(");\n"); + + sb.append("#endif\n"); + + return sb; + } + + private static void generateAccessOrderListenerMethodForGroupWrap( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append("\n") + .append(indent).append("private void ").append(accessOrderListenerMethodName(token)) + .append("(int remaining, string action)\n") + .append(indent).append("{\n") + .append(indent).append(INDENT).append("if (remaining == 0)\n") + .append(indent).append(INDENT).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction selectEmptyGroup = + fieldPrecedenceModel.interactionFactory().determineGroupIsEmpty(token); + + generateAccessOrderListener( + sb, + indent + TWO_INDENT, + "\" + action + \" count of repeating group", + fieldPrecedenceModel, + selectEmptyGroup); + + sb.append(indent).append(INDENT).append("}\n") + .append(indent).append(INDENT).append("else\n") + .append(indent).append(INDENT).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction selectNonEmptyGroup = + fieldPrecedenceModel.interactionFactory().determineGroupHasElements(token); + + generateAccessOrderListener( + sb, + indent + TWO_INDENT, + "\" + action + \" count of repeating group", + fieldPrecedenceModel, + selectNonEmptyGroup); + + sb.append(indent).append(INDENT).append("}\n") + .append(indent).append("}\n"); + } + + private static void generateAccessOrderListener( + final StringBuilder sb, + final String indent, + final String action, + final FieldPrecedenceModel fieldPrecedenceModel, + final FieldPrecedenceModel.CodecInteraction interaction) + { + if (interaction.isTopLevelBlockFieldAccess()) + { + sb.append(indent).append("if (codecState() == ") + .append(qualifiedStateCase(fieldPrecedenceModel.notWrappedState())) + .append(")\n") + .append(indent).append("{\n"); + generateAccessOrderException(sb, indent + INDENT, action, fieldPrecedenceModel, interaction); + sb.append(indent).append("}\n"); + } + else + { + sb.append(indent).append("switch (codecState())\n") + .append(indent).append("{\n"); + + fieldPrecedenceModel.forEachTransition(interaction, transitionGroup -> + { + transitionGroup.forEachStartState(startState -> + { + sb.append(indent).append(INDENT) + .append("case ").append(stateCaseForSwitchCase(startState)).append(":\n"); + }); + sb.append(indent).append(TWO_INDENT).append("codecState(") + .append(qualifiedStateCase(transitionGroup.endState())).append(");\n") + .append(indent).append(TWO_INDENT).append("break;\n"); + }); + + sb.append(indent).append(INDENT).append("default:\n"); + generateAccessOrderException(sb, indent + TWO_INDENT, action, fieldPrecedenceModel, interaction); + sb.append(indent).append("}\n"); + } + } + + private static void generateAccessOrderException( + final StringBuilder sb, + final String indent, + final String action, + final FieldPrecedenceModel fieldPrecedenceModel, + final FieldPrecedenceModel.CodecInteraction interaction) + { + sb.append(indent).append("throw new InvalidOperationException(") + .append("\"Illegal field access order. \" +\n") + .append(indent).append(INDENT) + .append("\"Cannot ").append(action).append(" \\\"").append(interaction.groupQualifiedName()) + .append("\\\" in state: \" + codecStateName(codecState()) +\n") + .append(indent).append(INDENT) + .append("\". Expected one of these transitions: [\" + codecStateTransitions(codecState()) +\n") + .append(indent).append(INDENT) + .append("\"]. Please see the diagram in the docs of the enum ") + .append(fieldPrecedenceModel.generatedRepresentationClassName()).append(".\");\n"); + } + + private static void generateAccessOrderListenerMethodForNextGroupElement( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append("\n"); + + sb.append(indent).append("private void OnNextElementAccessed()\n") + .append(indent).append("{\n") + .append(indent).append(INDENT).append("int remaining = ").append("_count - _index").append(";\n") + .append(indent).append(INDENT).append("if (remaining > 1)\n") + .append(indent).append(INDENT).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction selectNextElementInGroup = + fieldPrecedenceModel.interactionFactory().moveToNextElement(token); + + generateAccessOrderListener( + sb, + indent + TWO_INDENT, + "access next element in repeating group", + fieldPrecedenceModel, + selectNextElementInGroup); + + sb.append(indent).append(INDENT).append("}\n") + .append(indent).append(INDENT).append("else if (remaining == 1)\n") + .append(indent).append(INDENT).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction selectLastElementInGroup = + fieldPrecedenceModel.interactionFactory().moveToLastElement(token); + + generateAccessOrderListener( + sb, + indent + TWO_INDENT, + "access next element in repeating group", + fieldPrecedenceModel, + selectLastElementInGroup); + + sb.append(indent).append(INDENT).append("}\n") + .append(indent).append("}\n"); + } + + private static void generateAccessOrderListenerMethodForResetGroupCount( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append(indent).append("private void OnResetCountToIndex()\n") + .append(indent).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction resetCountToIndex = + fieldPrecedenceModel.interactionFactory().resetCountToIndex(token); + + generateAccessOrderListener( + sb, + indent + " ", + "reset count of repeating group", + fieldPrecedenceModel, + resetCountToIndex); + + sb.append(indent).append("}\n"); + } + + private static void generateAccessOrderListenerMethodForVarDataLength( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append("\n") + .append(indent).append("private void ") + .append(accessOrderListenerMethodName(token, "Length")).append("()\n") + .append(indent).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction accessLength = + fieldPrecedenceModel.interactionFactory().accessVarDataLength(token); + + generateAccessOrderListener( + sb, + indent + INDENT, + "decode length of var data", + fieldPrecedenceModel, + accessLength); + + sb.append(indent).append("}\n"); + } + + private static CharSequence generateDecoderWrapListener( + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + sb.append(indent).append("private void OnWrapForDecode(int actingVersion)\n") + .append(indent).append("{\n") + .append(indent).append(INDENT).append("switch(actingVersion)\n") + .append(indent).append(INDENT).append("{\n"); + + fieldPrecedenceModel.forEachWrappedStateByVersion((version, state) -> + { + sb.append(indent).append(TWO_INDENT).append("case ").append(version).append(":\n") + .append(indent).append(THREE_INDENT).append("codecState(") + .append(qualifiedStateCase(state)).append(");\n") + .append(indent).append(THREE_INDENT).append("break;\n"); + }); + + sb.append(indent).append(TWO_INDENT).append("default:\n") + .append(indent).append(THREE_INDENT).append("codecState(") + .append(qualifiedStateCase(fieldPrecedenceModel.latestVersionWrappedState())).append(");\n") + .append(indent).append(THREE_INDENT).append("break;\n") + .append(indent).append(INDENT).append("}\n") + .append(indent).append("}\n\n"); + + return sb; + } + + private CharSequence generateEncoderWrapListener( + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + sb.append("#if ").append(precedenceChecksFlagName).append("\n") + .append(indent).append("codecState(") + .append(qualifiedStateCase(fieldPrecedenceModel.latestVersionWrappedState())) + .append(");\n") + .append("#endif\n"); + return sb; + } + + private CharSequence generateFields( + final FieldPrecedenceModel fieldPrecedenceModel, + final List tokens, + final String indent) { final StringBuilder sb = new StringBuilder(); @@ -1258,26 +1979,32 @@ private CharSequence generateFields(final List tokens, final String inden generateFieldIdMethod(sb, signalToken, indent + INDENT); generateSinceActingDeprecated( - sb, indent, CSharpUtil.formatPropertyName(signalToken.name()), signalToken); + sb, indent + INDENT, CSharpUtil.formatPropertyName(signalToken.name()), signalToken); generateOffsetMethod(sb, signalToken, indent + INDENT); generateFieldMetaAttributeMethod(sb, signalToken, indent + INDENT); + generateAccessOrderListenerMethod(sb, fieldPrecedenceModel, indent + INDENT, signalToken); + switch (encodingToken.signal()) { case ENCODING: - sb.append(generatePrimitiveProperty(propertyName, signalToken, encodingToken, indent)); + sb.append(generatePrimitiveProperty(propertyName, signalToken, encodingToken, + fieldPrecedenceModel, indent)); break; case BEGIN_ENUM: - sb.append(generateEnumProperty(propertyName, signalToken, encodingToken, indent)); + sb.append(generateEnumProperty(propertyName, signalToken, encodingToken, + fieldPrecedenceModel, indent)); break; case BEGIN_SET: - sb.append(generateBitSetProperty(propertyName, signalToken, encodingToken, indent)); + sb.append(generateBitSetProperty(propertyName, signalToken, encodingToken, + fieldPrecedenceModel, indent)); break; case BEGIN_COMPOSITE: - sb.append(generateCompositeProperty(propertyName, signalToken, encodingToken, indent)); + sb.append(generateCompositeProperty(propertyName, signalToken, encodingToken, + fieldPrecedenceModel, indent)); break; default: @@ -1352,6 +2079,7 @@ private CharSequence generateEnumProperty( final String propertyName, final Token fieldToken, final Token typeToken, + final FieldPrecedenceModel fieldPrecedenceModel, final String indent) { final String enumName = formatClassName(typeToken.applicableTypeName()); @@ -1381,6 +2109,9 @@ private CharSequence generateEnumProperty( } else { + final CharSequence accessOrderListenerCall = generateAccessOrderListenerCall( + fieldPrecedenceModel, indent + TWO_INDENT, fieldToken); + return String.format("\n" + "%1$s" + indent + INDENT + "public %2$s %3$s\n" + @@ -1388,10 +2119,12 @@ private CharSequence generateEnumProperty( indent + INDENT + INDENT + "get\n" + indent + INDENT + INDENT + "{\n" + "%4$s" + + "%10$s" + indent + INDENT + INDENT + INDENT + "return (%5$s)_buffer.%6$sGet%8$s(_offset + %7$d);\n" + indent + INDENT + INDENT + "}\n" + indent + INDENT + INDENT + "set\n" + indent + INDENT + INDENT + "{\n" + + "%10$s" + indent + INDENT + INDENT + INDENT + "_buffer.%6$sPut%8$s(_offset + %7$d, (%9$s)value);\n" + indent + INDENT + INDENT + "}\n" + indent + INDENT + "}\n\n", @@ -1403,12 +2136,17 @@ private CharSequence generateEnumProperty( typePrefix, offset, byteOrderStr, - enumUnderlyingType); + enumUnderlyingType, + accessOrderListenerCall); } } private String generateBitSetProperty( - final String propertyName, final Token fieldToken, final Token typeToken, final String indent) + final String propertyName, + final Token fieldToken, + final Token typeToken, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent) { final String bitSetName = formatClassName(typeToken.applicableTypeName()); final int offset = typeToken.offset(); @@ -1416,6 +2154,8 @@ private String generateBitSetProperty( final ByteOrder byteOrder = typeToken.encoding().byteOrder(); final String byteOrderStr = generateByteOrder(byteOrder, typeToken.encoding().primitiveType().size()); final String typeName = cSharpTypeName(typeToken.encoding().primitiveType()); + final CharSequence accessOrderListenerCall = generateAccessOrderListenerCall( + fieldPrecedenceModel, indent + TWO_INDENT, fieldToken); return String.format("\n" + "%1$s" + @@ -1424,10 +2164,12 @@ private String generateBitSetProperty( indent + INDENT + INDENT + "get\n" + indent + INDENT + INDENT + "{\n" + "%4$s" + + "%10$s" + indent + INDENT + INDENT + INDENT + "return (%5$s)_buffer.%6$sGet%8$s(_offset + %7$d);\n" + indent + INDENT + INDENT + "}\n" + indent + INDENT + INDENT + "set\n" + indent + INDENT + INDENT + "{\n" + + "%10$s" + indent + INDENT + INDENT + INDENT + "_buffer.%6$sPut%8$s(_offset + %7$d, (%9$s)value);\n" + indent + INDENT + INDENT + "}\n" + indent + INDENT + "}\n", @@ -1439,14 +2181,21 @@ private String generateBitSetProperty( typePrefix, offset, byteOrderStr, - typeName); + typeName, + accessOrderListenerCall); } private Object generateCompositeProperty( - final String propertyName, final Token fieldToken, final Token typeToken, final String indent) + final String propertyName, + final Token fieldToken, + final Token typeToken, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent) { final String compositeName = CSharpUtil.formatClassName(typeToken.applicableTypeName()); final int offset = typeToken.offset(); + final CharSequence accessOrderListenerCall = generateAccessOrderListenerCall( + fieldPrecedenceModel, indent + THREE_INDENT, fieldToken); final StringBuilder sb = new StringBuilder(); sb.append(String.format("\n" + @@ -1462,6 +2211,7 @@ private Object generateCompositeProperty( indent + INDENT + INDENT + "get\n" + indent + INDENT + INDENT + "{\n" + "%4$s" + + "%7$s" + indent + INDENT + INDENT + INDENT + "_%5$s.Wrap(_buffer, _offset + %6$d, _actingVersion);\n" + indent + INDENT + INDENT + INDENT + "return _%5$s;\n" + indent + INDENT + INDENT + "}\n" + @@ -1471,7 +2221,8 @@ private Object generateCompositeProperty( toUpperFirstChar(propertyName), generateTypeFieldNotPresentCondition(fieldToken.version(), indent), toLowerFirstChar(propertyName), - offset)); + offset, + accessOrderListenerCall)); return sb; } @@ -1715,11 +2466,13 @@ private int writeTokenDisplay( { if (typeToken.encoding().primitiveType() == PrimitiveType.CHAR) { + append(sb, indent, "builder.Append(\"'\");"); append(sb, indent, "for (int i = 0; i < " + fieldName + "Length && this.Get" + fieldName + "(i) > 0; ++i)"); append(sb, indent, "{"); append(sb, indent, " builder.Append((char)this.Get" + fieldName + "(i));"); append(sb, indent, "}"); + append(sb, indent, "builder.Append(\"'\");"); } else { @@ -1804,7 +2557,8 @@ private CharSequence generateDisplay( final String name, final List tokens, final List groups, - final List varData) + final List varData, + final FieldPrecedenceModel fieldPrecedenceModel) { final StringBuilder sb = new StringBuilder(100); @@ -1818,6 +2572,15 @@ private CharSequence generateDisplay( append(sb, TWO_INDENT, " }"); sb.append('\n'); append(sb, TWO_INDENT, " int originalLimit = this.Limit;"); + if (null != fieldPrecedenceModel) + { + sb.append("#if ").append(precedenceChecksFlagName).append("\n"); + append(sb, TWO_INDENT, " CodecState originalState = _codecState;"); + sb.append(THREE_INDENT).append("_codecState = ") + .append(qualifiedStateCase(fieldPrecedenceModel.notWrappedState())).append(";\n"); + append(sb, TWO_INDENT, " OnWrapForDecode(_actingVersion);"); + sb.append("#endif\n"); + } append(sb, TWO_INDENT, " this.Limit = _offset + _actingBlockLength;"); append(sb, TWO_INDENT, " builder.Append(\"[" + name + "](sbeTemplateId=\");"); append(sb, TWO_INDENT, " builder.Append(" + name + ".TemplateId);"); @@ -1841,6 +2604,12 @@ private CharSequence generateDisplay( sb.append('\n'); appendDisplay(sb, tokens, groups, varData, THREE_INDENT); sb.append('\n'); + if (null != fieldPrecedenceModel) + { + sb.append("#if ").append(precedenceChecksFlagName).append("\n"); + append(sb, TWO_INDENT, " _codecState = originalState;"); + sb.append("#endif\n"); + } append(sb, TWO_INDENT, " this.Limit = originalLimit;"); sb.append('\n'); append(sb, TWO_INDENT, "}"); diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaGenerator.java index e40a02c6a1..7edf2e68c3 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaGenerator.java @@ -15,25 +15,23 @@ */ package uk.co.real_logic.sbe.generation.java; +import uk.co.real_logic.sbe.PrimitiveType; +import uk.co.real_logic.sbe.generation.CodeGenerator; +import uk.co.real_logic.sbe.generation.Generators; +import uk.co.real_logic.sbe.generation.common.FieldPrecedenceModel; +import uk.co.real_logic.sbe.generation.common.PrecedenceChecks; +import uk.co.real_logic.sbe.ir.*; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; import org.agrona.Strings; import org.agrona.Verify; +import org.agrona.collections.MutableBoolean; import org.agrona.generation.DynamicPackageOutputManager; import org.agrona.sbe.*; -import uk.co.real_logic.sbe.PrimitiveType; -import uk.co.real_logic.sbe.generation.CodeGenerator; -import uk.co.real_logic.sbe.generation.Generators; -import uk.co.real_logic.sbe.ir.*; import java.io.IOException; import java.io.Writer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Formatter; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.function.Function; import static uk.co.real_logic.sbe.SbeTool.JAVA_INTERFACE_PACKAGE; @@ -73,6 +71,9 @@ enum CodecType private final boolean shouldGenerateInterfaces; private final boolean shouldDecodeUnknownEnumValues; private final boolean shouldSupportTypesPackageNames; + private final PrecedenceChecks precedenceChecks; + private final String precedenceChecksFlagName; + private final String precedenceChecksPropName; private final Set packageNameByTypes = new HashSet<>(); /** @@ -120,6 +121,43 @@ public JavaGenerator( final boolean shouldDecodeUnknownEnumValues, final boolean shouldSupportTypesPackageNames, final DynamicPackageOutputManager outputManager) + { + this( + ir, + mutableBuffer, + readOnlyBuffer, + shouldGenerateGroupOrderAnnotation, + shouldGenerateInterfaces, + shouldDecodeUnknownEnumValues, + shouldSupportTypesPackageNames, + PrecedenceChecks.newInstance(new PrecedenceChecks.Context()), + outputManager + ); + } + + /** + * Create a new Java language {@link CodeGenerator}. + * + * @param ir for the messages and types. + * @param mutableBuffer implementation used for mutating underlying buffers. + * @param readOnlyBuffer implementation used for reading underlying buffers. + * @param shouldGenerateGroupOrderAnnotation in the codecs. + * @param shouldGenerateInterfaces for common methods. + * @param shouldDecodeUnknownEnumValues generate support for unknown enum values when decoding. + * @param shouldSupportTypesPackageNames generator support for types in their own package. + * @param precedenceChecks whether and how to generate field precedence checks. + * @param outputManager for generating the codecs to. + */ + public JavaGenerator( + final Ir ir, + final String mutableBuffer, + final String readOnlyBuffer, + final boolean shouldGenerateGroupOrderAnnotation, + final boolean shouldGenerateInterfaces, + final boolean shouldDecodeUnknownEnumValues, + final boolean shouldSupportTypesPackageNames, + final PrecedenceChecks precedenceChecks, + final DynamicPackageOutputManager outputManager) { Verify.notNull(ir, "ir"); Verify.notNull(outputManager, "outputManager"); @@ -137,6 +175,10 @@ public JavaGenerator( this.shouldGenerateGroupOrderAnnotation = shouldGenerateGroupOrderAnnotation; this.shouldGenerateInterfaces = shouldGenerateInterfaces; this.shouldDecodeUnknownEnumValues = shouldDecodeUnknownEnumValues; + + this.precedenceChecks = precedenceChecks; + this.precedenceChecksFlagName = precedenceChecks.context().precedenceChecksFlagName(); + this.precedenceChecksPropName = precedenceChecks.context().precedenceChecksPropName(); } /** @@ -230,20 +272,30 @@ public void generate() throws IOException final List varData = new ArrayList<>(); collectVarData(messageBody, i, varData); - generateDecoder(msgToken, fields, groups, varData, hasVarData); - generateEncoder(msgToken, fields, groups, varData, hasVarData); + final String decoderClassName = formatClassName(decoderName(msgToken.name())); + final String decoderStateClassName = decoderClassName + "#CodecStates"; + final FieldPrecedenceModel decoderPrecedenceModel = + precedenceChecks.createDecoderModel(decoderStateClassName, tokens); + generateDecoder(decoderClassName, msgToken, fields, groups, varData, hasVarData, decoderPrecedenceModel); + + final String encoderClassName = formatClassName(encoderName(msgToken.name())); + final String encoderStateClassName = encoderClassName + "#CodecStates"; + final FieldPrecedenceModel encoderPrecedenceModel = + precedenceChecks.createEncoderModel(encoderStateClassName, tokens); + generateEncoder(encoderClassName, msgToken, fields, groups, varData, hasVarData, encoderPrecedenceModel); } } private void generateEncoder( + final String className, final Token msgToken, final List fields, final List groups, final List varData, - final boolean hasVarData) + final boolean hasVarData, + final FieldPrecedenceModel fieldPrecedenceModel) throws IOException { - final String className = formatClassName(encoderName(msgToken.name())); final String implementsString = implementsInterface(MessageEncoderFlyweight.class.getSimpleName()); try (Writer out = outputManager.createOutput(className)) @@ -255,29 +307,519 @@ private void generateEncoder( generateAnnotations(BASE_INDENT, className, groups, out, this::encoderName); } out.append(generateDeclaration(className, implementsString, msgToken)); - out.append(generateEncoderFlyweightCode(className, msgToken)); + + out.append(generateFieldOrderStates(fieldPrecedenceModel)); + out.append(generateEncoderFlyweightCode(className, fieldPrecedenceModel, msgToken)); final StringBuilder sb = new StringBuilder(); - generateEncoderFields(sb, className, fields, BASE_INDENT); - generateEncoderGroups(sb, className, groups, BASE_INDENT, false); - generateEncoderVarData(sb, className, varData, BASE_INDENT); + generateEncoderFields(sb, className, fieldPrecedenceModel, fields, BASE_INDENT); + generateEncoderGroups(sb, className, fieldPrecedenceModel, groups, BASE_INDENT, false); + generateEncoderVarData(sb, className, fieldPrecedenceModel, varData, BASE_INDENT); generateEncoderDisplay(sb, decoderName(msgToken.name())); + generateFullyEncodedCheck(sb, fieldPrecedenceModel); out.append(sb); out.append("}\n"); } } + private static CharSequence qualifiedStateCase(final FieldPrecedenceModel.State state) + { + return "CodecStates." + state.name(); + } + + private static CharSequence stateCaseForSwitchCase(final FieldPrecedenceModel.State state) + { + return qualifiedStateCase(state); + } + + private static CharSequence unqualifiedStateCase(final FieldPrecedenceModel.State state) + { + return state.name(); + } + + private CharSequence generateFieldOrderStates(final FieldPrecedenceModel fieldPrecedenceModel) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + + sb.append(" private static final boolean ENABLE_BOUNDS_CHECKS = ") + .append("!Boolean.getBoolean(\"agrona.disable.bounds.checks\");\n\n"); + sb.append(" private static final boolean ") + .append(precedenceChecksFlagName).append(" = ") + .append("Boolean.parseBoolean(System.getProperty(\n") + .append(" \"").append(precedenceChecksPropName).append("\",\n") + .append(" Boolean.toString(ENABLE_BOUNDS_CHECKS)));\n\n"); + + sb.append(" /**\n"); + sb.append(" * The states in which a encoder/decoder/codec can live.\n"); + sb.append(" *\n"); + sb.append(" *

The state machine diagram below, encoded in the dot language, describes\n"); + sb.append(" * the valid state transitions according to the order in which fields may be\n"); + sb.append(" * accessed safely. Tools such as PlantUML and Graphviz can render it.\n"); + sb.append(" *\n"); + sb.append(" *

{@code\n");
+        fieldPrecedenceModel.generateGraph(sb, "     *   ");
+        sb.append("     * }
\n"); + sb.append(" */\n"); + sb.append(" private static class CodecStates\n") + .append(" {\n"); + fieldPrecedenceModel.forEachStateOrderedByStateNumber(state -> + { + sb.append(" private static final int ") + .append(unqualifiedStateCase(state)) + .append(" = ").append(state.number()) + .append(";\n"); + }); + + sb.append("\n").append(" private static final String[] STATE_NAME_LOOKUP =\n") + .append(" {\n"); + fieldPrecedenceModel.forEachStateOrderedByStateNumber(state -> + { + sb.append(" \"").append(state.name()).append("\",\n"); + }); + sb.append(" };\n\n"); + + sb.append(" private static final String[] STATE_TRANSITIONS_LOOKUP =\n") + .append(" {\n"); + fieldPrecedenceModel.forEachStateOrderedByStateNumber(state -> + { + sb.append(" \""); + final MutableBoolean isFirst = new MutableBoolean(true); + final Set transitionDescriptions = new HashSet<>(); + fieldPrecedenceModel.forEachTransitionFrom(state, transitionGroup -> + { + if (transitionDescriptions.add(transitionGroup.exampleCode())) + { + if (isFirst.get()) + { + isFirst.set(false); + } + else + { + sb.append(", "); + } + + sb.append("\\\"").append(transitionGroup.exampleCode()).append("\\\""); + } + }); + sb.append("\",\n"); + }); + sb.append(" };\n\n"); + + sb.append(" private static String name(final int state)\n") + .append(" {\n") + .append(" return STATE_NAME_LOOKUP[state];\n") + .append(" }\n\n"); + + sb.append(" private static String transitions(final int state)\n") + .append(" {\n") + .append(" return STATE_TRANSITIONS_LOOKUP[state];\n") + .append(" }\n"); + + sb.append(" }\n\n"); + + sb.append(" private int codecState = ") + .append(qualifiedStateCase(fieldPrecedenceModel.notWrappedState())) + .append(";\n\n"); + + sb.append(" private int codecState()\n") + .append(" {\n") + .append(" return codecState;\n") + .append(" }\n\n"); + + sb.append(" private void codecState(int newState)\n") + .append(" {\n") + .append(" codecState = newState;\n") + .append(" }\n\n"); + + return sb; + } + + private void generateFullyEncodedCheck( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append("\n"); + + sb.append(" public void checkEncodingIsComplete()\n") + .append(" {\n") + .append(" if (").append(precedenceChecksFlagName).append(")\n") + .append(" {\n") + .append(" switch (codecState)\n") + .append(" {\n"); + + fieldPrecedenceModel.forEachTerminalEncoderState(state -> + { + sb.append(" case ").append(stateCaseForSwitchCase(state)).append(":\n") + .append(" return;\n"); + }); + + sb.append(" default:\n") + .append(" throw new IllegalStateException(\"Not fully encoded, current state: \" +\n") + .append(" CodecStates.name(codecState) + \", allowed transitions: \" +\n") + .append(" CodecStates.transitions(codecState));\n") + .append(" }\n") + .append(" }\n") + .append(" }\n\n"); + } + + private static String accessOrderListenerMethodName(final Token token) + { + return "on" + Generators.toUpperFirstChar(token.name()) + "Accessed"; + } + + private static String accessOrderListenerMethodName(final Token token, final String suffix) + { + return "on" + Generators.toUpperFirstChar(token.name()) + suffix + "Accessed"; + } + + private static void generateAccessOrderListenerMethod( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append("\n") + .append(indent).append("private void ").append(accessOrderListenerMethodName(token)).append("()\n") + .append(indent).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction fieldAccess = + fieldPrecedenceModel.interactionFactory().accessField(token); + + generateAccessOrderListener( + sb, + indent + " ", + "access field", + fieldPrecedenceModel, + fieldAccess); + + sb.append(indent).append("}\n"); + } + + private CharSequence generateAccessOrderListenerCall( + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token, + final String... arguments) + { + return generateAccessOrderListenerCall( + fieldPrecedenceModel, + indent, + accessOrderListenerMethodName(token), + arguments); + } + + private CharSequence generateAccessOrderListenerCall( + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final String methodName, + final String... arguments) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + sb.append(indent).append("if (").append(precedenceChecksFlagName).append(")\n") + .append(indent).append("{\n") + .append(indent).append(" ").append(methodName).append("("); + + for (int i = 0; i < arguments.length; i++) + { + if (i > 0) + { + sb.append(", "); + } + sb.append(arguments[i]); + } + sb.append(");\n"); + + sb.append(indent).append("}\n\n"); + + return sb; + } + + private static void generateAccessOrderListenerMethodForGroupWrap( + final StringBuilder sb, + final String action, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append("\n") + .append(indent).append("private void ").append(accessOrderListenerMethodName(token)) + .append("(final int count)\n") + .append(indent).append("{\n") + .append(indent).append(" if (count == 0)\n") + .append(indent).append(" {\n"); + + final FieldPrecedenceModel.CodecInteraction selectEmptyGroup = + fieldPrecedenceModel.interactionFactory().determineGroupIsEmpty(token); + + generateAccessOrderListener( + sb, + indent + " ", + action + " count of repeating group", + fieldPrecedenceModel, + selectEmptyGroup); + + sb.append(indent).append(" }\n") + .append(indent).append(" else\n") + .append(indent).append(" {\n"); + + final FieldPrecedenceModel.CodecInteraction selectNonEmptyGroup = + fieldPrecedenceModel.interactionFactory().determineGroupHasElements(token); + + generateAccessOrderListener( + sb, + indent + " ", + action + " count of repeating group", + fieldPrecedenceModel, + selectNonEmptyGroup); + + sb.append(indent).append(" }\n") + .append(indent).append("}\n"); + } + + private static void generateAccessOrderListener( + final StringBuilder sb, + final String indent, + final String action, + final FieldPrecedenceModel fieldPrecedenceModel, + final FieldPrecedenceModel.CodecInteraction interaction) + { + if (interaction.isTopLevelBlockFieldAccess()) + { + sb.append(indent).append("if (codecState() == ") + .append(qualifiedStateCase(fieldPrecedenceModel.notWrappedState())) + .append(")\n") + .append(indent).append("{\n"); + generateAccessOrderException(sb, indent + " ", action, fieldPrecedenceModel, interaction); + sb.append(indent).append("}\n"); + } + else + { + sb.append(indent).append("switch (codecState())\n") + .append(indent).append("{\n"); + + fieldPrecedenceModel.forEachTransition(interaction, transitionGroup -> + { + transitionGroup.forEachStartState(startState -> + sb.append(indent).append(" case ").append(stateCaseForSwitchCase(startState)).append(":\n")); + sb.append(indent).append(" codecState(") + .append(qualifiedStateCase(transitionGroup.endState())).append(");\n") + .append(indent).append(" break;\n"); + }); + + sb.append(indent).append(" default:\n"); + generateAccessOrderException(sb, indent + " ", action, fieldPrecedenceModel, interaction); + sb.append(indent).append("}\n"); + } + } + + private static void generateAccessOrderException( + final StringBuilder sb, + final String indent, + final String action, + final FieldPrecedenceModel fieldPrecedenceModel, + final FieldPrecedenceModel.CodecInteraction interaction) + { + sb.append(indent).append("throw new IllegalStateException(") + .append("\"Illegal field access order. \" +\n") + .append(indent) + .append(" \"Cannot ").append(action).append(" \\\"").append(interaction.groupQualifiedName()) + .append("\\\" in state: \" + CodecStates.name(codecState()) +\n") + .append(indent) + .append(" \". Expected one of these transitions: [\" + CodecStates.transitions(codecState()) +\n") + .append(indent) + .append(" \"]. Please see the diagram in the Javadoc of the class ") + .append(fieldPrecedenceModel.generatedRepresentationClassName()).append(".\");\n"); + } + + private static void generateAccessOrderListenerMethodForNextGroupElement( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append(indent).append("private void onNextElementAccessed()\n") + .append(indent).append("{\n") + .append(indent).append(" final int remaining = ").append("count - index").append(";\n") + .append(indent).append(" if (remaining > 1)\n") + .append(indent).append(" {\n"); + + final FieldPrecedenceModel.CodecInteraction selectNextElementInGroup = + fieldPrecedenceModel.interactionFactory().moveToNextElement(token); + + generateAccessOrderListener( + sb, + indent + " ", + "access next element in repeating group", + fieldPrecedenceModel, + selectNextElementInGroup); + + sb.append(indent).append(" }\n") + .append(indent).append(" else if (remaining == 1)\n") + .append(indent).append(" {\n"); + + final FieldPrecedenceModel.CodecInteraction selectLastElementInGroup = + fieldPrecedenceModel.interactionFactory().moveToLastElement(token); + + generateAccessOrderListener( + sb, + indent + " ", + "access next element in repeating group", + fieldPrecedenceModel, + selectLastElementInGroup); + + sb.append(indent).append(" }\n") + .append(indent).append("}\n\n"); + } + + private static void generateAccessOrderListenerMethodForResetGroupCount( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append(indent).append("private void onResetCountToIndex()\n") + .append(indent).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction resetCountToIndex = + fieldPrecedenceModel.interactionFactory().resetCountToIndex(token); + + generateAccessOrderListener( + sb, + indent + " ", + "reset count of repeating group", + fieldPrecedenceModel, + resetCountToIndex); + + sb.append(indent).append("}\n\n"); + } + + private static void generateAccessOrderListenerMethodForVarDataLength( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent, + final Token token) + { + if (null == fieldPrecedenceModel) + { + return; + } + + sb.append("\n") + .append(indent).append("void ").append(accessOrderListenerMethodName(token, "Length")).append("()\n") + .append(indent).append("{\n"); + + final FieldPrecedenceModel.CodecInteraction accessLength = + fieldPrecedenceModel.interactionFactory().accessVarDataLength(token); + + generateAccessOrderListener( + sb, + indent + INDENT, + "decode length of var data", + fieldPrecedenceModel, + accessLength); + + sb.append(indent).append("}\n"); + } + + private static CharSequence generateDecoderWrapListener( + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + sb.append(indent).append("private void onWrap(final int actingVersion)\n") + .append(indent).append("{\n") + .append(indent).append(" switch(actingVersion)\n") + .append(indent).append(" {\n"); + + fieldPrecedenceModel.forEachWrappedStateByVersion((version, state) -> + { + sb.append(indent).append(" case ").append(version).append(":\n") + .append(indent).append(" codecState(") + .append(qualifiedStateCase(state)).append(");\n") + .append(indent).append(" break;\n"); + }); + + sb.append(indent).append(" default:\n") + .append(indent).append(" codecState(") + .append(qualifiedStateCase(fieldPrecedenceModel.latestVersionWrappedState())).append(");\n") + .append(indent).append(" break;\n") + .append(indent).append(" }\n") + .append(indent).append("}\n\n"); + + return sb; + } + + private CharSequence generateEncoderWrapListener( + final FieldPrecedenceModel fieldPrecedenceModel, + final String indent) + { + if (null == fieldPrecedenceModel) + { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + sb.append(indent).append("if (").append(precedenceChecksFlagName).append(")") + .append("\n").append(indent).append("{\n") + .append(indent).append(" codecState(") + .append(qualifiedStateCase(fieldPrecedenceModel.latestVersionWrappedState())) + .append(");\n") + .append(indent).append("}\n\n"); + return sb; + } + private void generateDecoder( + final String className, final Token msgToken, final List fields, final List groups, final List varData, - final boolean hasVarData) + final boolean hasVarData, + final FieldPrecedenceModel fieldPrecedenceModel) throws IOException { - final String className = formatClassName(decoderName(msgToken.name())); final String implementsString = implementsInterface(MessageDecoderFlyweight.class.getSimpleName()); try (Writer out = outputManager.createOutput(className)) @@ -288,13 +830,15 @@ private void generateDecoder( { generateAnnotations(BASE_INDENT, className, groups, out, this::decoderName); } + out.append(generateDeclaration(className, implementsString, msgToken)); - out.append(generateDecoderFlyweightCode(className, msgToken)); + out.append(generateFieldOrderStates(fieldPrecedenceModel)); + out.append(generateDecoderFlyweightCode(fieldPrecedenceModel, className, msgToken)); final StringBuilder sb = new StringBuilder(); - generateDecoderFields(sb, fields, BASE_INDENT); - generateDecoderGroups(sb, className, groups, BASE_INDENT, false); - generateDecoderVarData(sb, varData, BASE_INDENT); + generateDecoderFields(sb, fieldPrecedenceModel, fields, BASE_INDENT); + generateDecoderGroups(sb, fieldPrecedenceModel, className, groups, BASE_INDENT, false); + generateDecoderVarData(sb, fieldPrecedenceModel, varData, BASE_INDENT); generateDecoderDisplay(sb, msgToken.name(), fields, groups, varData); generateMessageLength(sb, className, true, groups, varData, BASE_INDENT); @@ -306,7 +850,7 @@ private void generateDecoder( private void generateDecoderGroups( final StringBuilder sb, - final String outerClassName, + final FieldPrecedenceModel fieldPrecedenceModel, final String outerClassName, final List tokens, final String indent, final boolean isSubGroup) throws IOException @@ -335,18 +879,19 @@ private void generateDecoderGroups( final List varData = new ArrayList<>(); i = collectVarData(tokens, i, varData); - generateGroupDecoderProperty(sb, groupName, groupToken, indent, isSubGroup); + generateGroupDecoderProperty(sb, groupName, fieldPrecedenceModel, groupToken, indent, isSubGroup); generateTypeJavadoc(sb, indent + INDENT, groupToken); if (shouldGenerateGroupOrderAnnotation) { generateAnnotations(indent + INDENT, groupName, groups, sb, this::decoderName); } - generateGroupDecoderClassHeader(sb, groupName, outerClassName, tokens, groups, index, indent + INDENT); + generateGroupDecoderClassHeader(sb, groupName, outerClassName, fieldPrecedenceModel, groupToken, + tokens, groups, index, indent + INDENT); - generateDecoderFields(sb, fields, indent + INDENT); - generateDecoderGroups(sb, outerClassName, groups, indent + INDENT, true); - generateDecoderVarData(sb, varData, indent + INDENT); + generateDecoderFields(sb, fieldPrecedenceModel, fields, indent + INDENT); + generateDecoderGroups(sb, fieldPrecedenceModel, outerClassName, groups, indent + INDENT, true); + generateDecoderVarData(sb, fieldPrecedenceModel, varData, indent + INDENT); appendGroupInstanceDecoderDisplay(sb, fields, groups, varData, indent + INDENT); generateMessageLength(sb, groupName, false, groups, varData, indent + INDENT); @@ -358,6 +903,7 @@ private void generateDecoderGroups( private void generateEncoderGroups( final StringBuilder sb, final String outerClassName, + final FieldPrecedenceModel fieldPrecedenceModel, final List tokens, final String indent, final boolean isSubGroup) throws IOException @@ -387,18 +933,20 @@ private void generateEncoderGroups( final List varData = new ArrayList<>(); i = collectVarData(tokens, i, varData); - generateGroupEncoderProperty(sb, groupName, groupToken, indent, isSubGroup); + generateGroupEncoderProperty(sb, groupName, fieldPrecedenceModel, groupToken, indent, isSubGroup); generateTypeJavadoc(sb, indent + INDENT, groupToken); if (shouldGenerateGroupOrderAnnotation) { generateAnnotations(indent + INDENT, groupClassName, groups, sb, this::encoderName); } - generateGroupEncoderClassHeader(sb, groupName, outerClassName, tokens, groups, index, indent + INDENT); + generateGroupEncoderClassHeader( + sb, groupName, outerClassName, fieldPrecedenceModel, groupToken, + tokens, groups, index, indent + INDENT); - generateEncoderFields(sb, groupClassName, fields, indent + INDENT); - generateEncoderGroups(sb, outerClassName, groups, indent + INDENT, true); - generateEncoderVarData(sb, groupClassName, varData, indent + INDENT); + generateEncoderFields(sb, groupClassName, fieldPrecedenceModel, fields, indent + INDENT); + generateEncoderGroups(sb, outerClassName, fieldPrecedenceModel, groups, indent + INDENT, true); + generateEncoderVarData(sb, groupClassName, fieldPrecedenceModel, varData, indent + INDENT); sb.append(indent).append(" }\n"); } @@ -408,6 +956,8 @@ private void generateGroupDecoderClassHeader( final StringBuilder sb, final String groupName, final String parentMessageClassName, + final FieldPrecedenceModel fieldPrecedenceModel, + final Token groupToken, final List tokens, final List subGroupTokens, final int index, @@ -452,15 +1002,18 @@ private void generateGroupDecoderClassHeader( .append(indent).append(" parentMessage.limit(limit + HEADER_SIZE);\n") .append(indent).append(" blockLength = ").append(blockLenCast).append(blockLengthGet).append(";\n") .append(indent).append(" count = ").append(numInGroupCast).append(numInGroupGet).append(";\n") - .append(indent).append(" }\n"); + .append(indent).append(" }\n\n"); - sb.append("\n") - .append(indent).append(" public ").append(className).append(" next()\n") + + generateAccessOrderListenerMethodForNextGroupElement(sb, fieldPrecedenceModel, indent + " ", groupToken); + + sb.append(indent).append(" public ").append(className).append(" next()\n") .append(indent).append(" {\n") .append(indent).append(" if (index >= count)\n") .append(indent).append(" {\n") .append(indent).append(" throw new java.util.NoSuchElementException();\n") .append(indent).append(" }\n\n") + .append(generateAccessOrderListenerCall(fieldPrecedenceModel, indent + " ", "onNextElementAccessed")) .append(indent).append(" offset = parentMessage.limit();\n") .append(indent).append(" parentMessage.limit(offset + blockLength);\n") .append(indent).append(" ++index;\n\n") @@ -508,12 +1061,29 @@ private void generateGroupDecoderClassHeader( .append(indent).append(" {\n") .append(indent).append(" return index < count;\n") .append(indent).append(" }\n"); + + if (null != fieldPrecedenceModel) + { + sb.append("\n") + .append(indent).append(" private int codecState()\n") + .append(indent).append(" {\n") + .append(indent).append(" return parentMessage.codecState();\n") + .append(indent).append(" }\n"); + + sb.append("\n") + .append(indent).append(" private void codecState(final int newState)\n") + .append(indent).append(" {\n") + .append(indent).append(" parentMessage.codecState(newState);\n") + .append(indent).append(" }\n"); + } } private void generateGroupEncoderClassHeader( final StringBuilder sb, final String groupName, final String parentMessageClassName, + final FieldPrecedenceModel fieldPrecedenceModel, + final Token groupToken, final List tokens, final List subGroupTokens, final int index, @@ -566,7 +1136,7 @@ private void generateGroupEncoderClassHeader( ind + " parentMessage.limit(limit + HEADER_SIZE);\n" + ind + " %5$s;\n" + ind + " %6$s;\n" + - ind + " }\n", + ind + " }\n\n", parentMessageClassName, mutableBuffer, numInGroupToken.encoding().applicableMinValue().longValue(), @@ -574,9 +1144,12 @@ private void generateGroupEncoderClassHeader( blockLengthPut, numInGroupPut); - sb.append("\n") - .append(ind).append(" public ").append(encoderName(groupName)).append(" next()\n") + generateAccessOrderListenerMethodForNextGroupElement(sb, fieldPrecedenceModel, ind + " ", groupToken); + generateAccessOrderListenerMethodForResetGroupCount(sb, fieldPrecedenceModel, ind + " ", groupToken); + + sb.append(ind).append(" public ").append(encoderName(groupName)).append(" next()\n") .append(ind).append(" {\n") + .append(generateAccessOrderListenerCall(fieldPrecedenceModel, ind + " ", "onNextElementAccessed")) .append(ind).append(" if (index >= count)\n") .append(ind).append(" {\n") .append(ind).append(" throw new java.util.NoSuchElementException();\n") @@ -585,15 +1158,15 @@ private void generateGroupEncoderClassHeader( .append(ind).append(" parentMessage.limit(offset + sbeBlockLength());\n") .append(ind).append(" ++index;\n\n") .append(ind).append(" return this;\n") - .append(ind).append(" }\n"); + .append(ind).append(" }\n\n"); final String countOffset = "initialLimit + " + numInGroupToken.offset(); final String resetCountPut = generatePut( numInGroupTypeCast, countOffset, numInGroupValue, byteOrderString(numInGroupToken.encoding())); - sb.append("\n") - .append(ind).append(" public int resetCountToIndex()\n") + sb.append(ind).append(" public int resetCountToIndex()\n") .append(ind).append(" {\n") + .append(generateAccessOrderListenerCall(fieldPrecedenceModel, ind + " ", "onResetCountToIndex")) .append(ind).append(" count = index;\n") .append(ind).append(" ").append(resetCountPut).append(";\n\n") .append(ind).append(" return count;\n") @@ -618,6 +1191,21 @@ private void generateGroupEncoderClassHeader( .append(ind).append(" {\n") .append(ind).append(" return ").append(blockLength).append(";\n") .append(ind).append(" }\n"); + + if (null != fieldPrecedenceModel) + { + sb.append("\n") + .append(ind).append(" private int codecState()\n") + .append(ind).append(" {\n") + .append(ind).append(" return parentMessage.codecState();\n") + .append(ind).append(" }\n"); + + sb.append("\n") + .append(ind).append(" private void codecState(final int newState)\n") + .append(ind).append(" {\n") + .append(ind).append(" parentMessage.codecState(newState);\n") + .append(ind).append(" }\n"); + } } private static String primitiveTypeName(final Token token) @@ -728,9 +1316,10 @@ private void generateGroupEncoderClassDeclaration( sb.append(indent).append(" }\n"); } - private static void generateGroupDecoderProperty( + private void generateGroupDecoderProperty( final StringBuilder sb, final String groupName, + final FieldPrecedenceModel fieldPrecedenceModel, final Token token, final String indent, final boolean isSubGroup) @@ -772,22 +1361,27 @@ private static void generateGroupDecoderProperty( indent + " return " + propertyName + ";\n" + indent + " }\n\n"; + generateAccessOrderListenerMethodForGroupWrap(sb, "decode", fieldPrecedenceModel, indent + " ", token); + generateFlyweightPropertyJavadoc(sb, indent + INDENT, token, className); new Formatter(sb).format("\n" + indent + " public %1$s %2$s()\n" + indent + " {\n" + "%3$s" + indent + " %2$s.wrap(buffer);\n" + + "%4$s" + indent + " return %2$s;\n" + indent + " }\n", className, propertyName, - actingVersionGuard); + actingVersionGuard, + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + " ", token, propertyName + ".count")); } private void generateGroupEncoderProperty( final StringBuilder sb, final String groupName, + final FieldPrecedenceModel fieldPrecedenceModel, final Token token, final String indent, final boolean isSubGroup) @@ -812,19 +1406,26 @@ private void generateGroupEncoderProperty( formatPropertyName(groupName), token.id()); + generateAccessOrderListenerMethodForGroupWrap(sb, "encode", fieldPrecedenceModel, indent + " ", token); + generateGroupEncodePropertyJavadoc(sb, indent + INDENT, token, className); new Formatter(sb).format("\n" + indent + " public %1$s %2$sCount(final int count)\n" + indent + " {\n" + + "%3$s" + indent + " %2$s.wrap(buffer, count);\n" + indent + " return %2$s;\n" + indent + " }\n", className, - propertyName); + propertyName, + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + " ", token, "count")); } private void generateDecoderVarData( - final StringBuilder sb, final List tokens, final String indent) + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final List tokens, + final String indent) { for (int i = 0, size = tokens.size(); i < size;) { @@ -855,24 +1456,39 @@ private void generateDecoderVarData( .append(indent).append(" return ").append(sizeOfLengthField).append(";\n") .append(indent).append(" }\n"); + generateAccessOrderListenerMethodForVarDataLength(sb, fieldPrecedenceModel, indent + " ", token); + + final CharSequence lengthAccessOrderListenerCall = generateAccessOrderListenerCall( + fieldPrecedenceModel, indent + " ", accessOrderListenerMethodName(token, "Length")); + + generateAccessOrderListenerMethod(sb, fieldPrecedenceModel, indent + " ", token); + sb.append("\n") .append(indent).append(" public int ").append(methodPropName).append("Length()\n") .append(indent).append(" {\n") .append(generateArrayFieldNotPresentCondition(token.version(), indent)) + .append(lengthAccessOrderListenerCall) .append(indent).append(" final int limit = parentMessage.limit();\n") .append(indent).append(" return ").append(PrimitiveType.UINT32 == lengthType ? "(int)" : "") .append(generateGet(lengthType, "limit", byteOrderStr)).append(";\n") .append(indent).append(" }\n"); - generateDataDecodeMethods( - sb, token, propertyName, sizeOfLengthField, lengthType, byteOrderStr, characterEncoding, indent); + final CharSequence accessOrderListenerCall = + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + " ", token); + + generateDataDecodeMethods(sb, token, propertyName, sizeOfLengthField, lengthType, + byteOrderStr, characterEncoding, accessOrderListenerCall, indent); i += token.componentTokenCount(); } } private void generateEncoderVarData( - final StringBuilder sb, final String className, final List tokens, final String indent) + final StringBuilder sb, + final String className, + final FieldPrecedenceModel fieldPrecedenceModel, + final List tokens, + final String indent) { for (int i = 0, size = tokens.size(); i < size;) { @@ -903,9 +1519,14 @@ private void generateEncoderVarData( .append(sizeOfLengthField).append(";\n") .append(indent).append(" }\n"); + generateAccessOrderListenerMethod(sb, fieldPrecedenceModel, indent + " ", token); + final CharSequence accessOrderListenerCall = + generateAccessOrderListenerCall(fieldPrecedenceModel, indent + " ", token); + generateDataEncodeMethods( sb, propertyName, + accessOrderListenerCall, sizeOfLengthField, maxLengthValue, lengthEncoding.primitiveType(), @@ -926,12 +1547,14 @@ private void generateDataDecodeMethods( final PrimitiveType lengthType, final String byteOrderStr, final String characterEncoding, + final CharSequence accessOrderListenerCall, final String indent) { new Formatter(sb).format("\n" + indent + " public int skip%1$s()\n" + indent + " {\n" + "%2$s" + + "%6$s" + indent + " final int headerLength = %3$d;\n" + indent + " final int limit = parentMessage.limit();\n" + indent + " final int dataLength = %4$s%5$s;\n" + @@ -943,7 +1566,8 @@ private void generateDataDecodeMethods( generateStringNotPresentConditionForAppendable(token.version(), indent), sizeOfLengthField, PrimitiveType.UINT32 == lengthType ? "(int)" : "", - generateGet(lengthType, "limit", byteOrderStr)); + generateGet(lengthType, "limit", byteOrderStr), + accessOrderListenerCall); generateVarDataTypedDecoder( sb, @@ -953,6 +1577,7 @@ private void generateDataDecodeMethods( mutableBuffer, lengthType, byteOrderStr, + accessOrderListenerCall, indent); generateVarDataTypedDecoder( @@ -963,9 +1588,18 @@ private void generateDataDecodeMethods( "byte[]", lengthType, byteOrderStr, + accessOrderListenerCall, indent); - generateVarDataWrapDecoder(sb, token, propertyName, sizeOfLengthField, lengthType, byteOrderStr, indent); + generateVarDataWrapDecoder( + sb, + token, + propertyName, + sizeOfLengthField, + lengthType, + byteOrderStr, + accessOrderListenerCall, + indent); if (null != characterEncoding) { @@ -973,6 +1607,7 @@ private void generateDataDecodeMethods( indent + " public String %1$s()\n" + indent + " {\n" + "%2$s" + + "%7$s" + indent + " final int headerLength = %3$d;\n" + indent + " final int limit = parentMessage.limit();\n" + indent + " final int dataLength = %4$s%5$s;\n" + @@ -990,7 +1625,8 @@ private void generateDataDecodeMethods( sizeOfLengthField, PrimitiveType.UINT32 == lengthType ? "(int)" : "", generateGet(lengthType, "limit", byteOrderStr), - charset(characterEncoding)); + charset(characterEncoding), + accessOrderListenerCall); if (isAsciiEncoding(characterEncoding)) { @@ -998,6 +1634,7 @@ private void generateDataDecodeMethods( indent + " public int get%1$s(final Appendable appendable)\n" + indent + " {\n" + "%2$s" + + "%6$s" + indent + " final int headerLength = %3$d;\n" + indent + " final int limit = parentMessage.limit();\n" + indent + " final int dataLength = %4$s%5$s;\n" + @@ -1010,7 +1647,8 @@ private void generateDataDecodeMethods( generateStringNotPresentConditionForAppendable(token.version(), indent), sizeOfLengthField, PrimitiveType.UINT32 == lengthType ? "(int)" : "", - generateGet(lengthType, "limit", byteOrderStr)); + generateGet(lengthType, "limit", byteOrderStr), + accessOrderListenerCall); } } } @@ -1022,12 +1660,14 @@ private void generateVarDataWrapDecoder( final int sizeOfLengthField, final PrimitiveType lengthType, final String byteOrderStr, + final CharSequence accessOrderListenerCall, final String indent) { new Formatter(sb).format("\n" + indent + " public void wrap%s(final %s wrapBuffer)\n" + indent + " {\n" + "%s" + + "%s" + indent + " final int headerLength = %d;\n" + indent + " final int limit = parentMessage.limit();\n" + indent + " final int dataLength = %s%s;\n" + @@ -1037,6 +1677,7 @@ private void generateVarDataWrapDecoder( propertyName, readOnlyBuffer, generateWrapFieldNotPresentCondition(token.version(), indent), + accessOrderListenerCall, sizeOfLengthField, PrimitiveType.UINT32 == lengthType ? "(int)" : "", generateGet(lengthType, "limit", byteOrderStr)); @@ -1045,6 +1686,7 @@ private void generateVarDataWrapDecoder( private void generateDataEncodeMethods( final StringBuilder sb, final String propertyName, + final CharSequence accessOrderListenerCall, final int sizeOfLengthField, final int maxLengthValue, final PrimitiveType lengthType, @@ -1057,6 +1699,7 @@ private void generateDataEncodeMethods( sb, className, propertyName, + accessOrderListenerCall, sizeOfLengthField, maxLengthValue, readOnlyBuffer, @@ -1068,6 +1711,7 @@ private void generateDataEncodeMethods( sb, className, propertyName, + accessOrderListenerCall, sizeOfLengthField, maxLengthValue, "byte[]", @@ -1080,6 +1724,7 @@ private void generateDataEncodeMethods( generateCharArrayEncodeMethods( sb, propertyName, + accessOrderListenerCall, sizeOfLengthField, maxLengthValue, lengthType, @@ -1093,6 +1738,7 @@ private void generateDataEncodeMethods( private void generateCharArrayEncodeMethods( final StringBuilder sb, final String propertyName, + final CharSequence accessOrderListenerCall, final int sizeOfLengthField, final int maxLengthValue, final PrimitiveType lengthType, @@ -1113,6 +1759,7 @@ private void generateCharArrayEncodeMethods( indent + " {\n" + indent + " throw new IllegalStateException(\"length > maxValue for type: \" + length);\n" + indent + " }\n\n" + + "%6$s" + indent + " final int headerLength = %4$d;\n" + indent + " final int limit = parentMessage.limit();\n" + indent + " parentMessage.limit(limit + headerLength + length);\n" + @@ -1124,7 +1771,8 @@ private void generateCharArrayEncodeMethods( formatPropertyName(propertyName), maxLengthValue, sizeOfLengthField, - generatePut(lengthPutType, "limit", "length", byteOrderStr)); + generatePut(lengthPutType, "limit", "length", byteOrderStr), + accessOrderListenerCall); new Formatter(sb).format("\n" + indent + " public %1$s %2$s(final CharSequence value)\n" + @@ -1134,6 +1782,7 @@ private void generateCharArrayEncodeMethods( indent + " {\n" + indent + " throw new IllegalStateException(\"length > maxValue for type: \" + length);\n" + indent + " }\n\n" + + "%6$s" + indent + " final int headerLength = %4$d;\n" + indent + " final int limit = parentMessage.limit();\n" + indent + " parentMessage.limit(limit + headerLength + length);\n" + @@ -1145,7 +1794,8 @@ private void generateCharArrayEncodeMethods( formatPropertyName(propertyName), maxLengthValue, sizeOfLengthField, - generatePut(lengthPutType, "limit", "length", byteOrderStr)); + generatePut(lengthPutType, "limit", "length", byteOrderStr), + accessOrderListenerCall); } else { @@ -1159,6 +1809,7 @@ private void generateCharArrayEncodeMethods( indent + " {\n" + indent + " throw new IllegalStateException(\"length > maxValue for type: \" + length);\n" + indent + " }\n\n" + + "%7$s" + indent + " final int headerLength = %5$d;\n" + indent + " final int limit = parentMessage.limit();\n" + indent + " parentMessage.limit(limit + headerLength + length);\n" + @@ -1171,7 +1822,8 @@ private void generateCharArrayEncodeMethods( charset(characterEncoding), maxLengthValue, sizeOfLengthField, - generatePut(lengthPutType, "limit", "length", byteOrderStr)); + generatePut(lengthPutType, "limit", "length", byteOrderStr), + accessOrderListenerCall); } } @@ -1183,12 +1835,14 @@ private void generateVarDataTypedDecoder( final String exchangeType, final PrimitiveType lengthType, final String byteOrderStr, + final CharSequence accessOrderListenerCall, final String indent) { new Formatter(sb).format("\n" + indent + " public int get%s(final %s dst, final int dstOffset, final int length)\n" + indent + " {\n" + "%s" + + "%s" + indent + " final int headerLength = %d;\n" + indent + " final int limit = parentMessage.limit();\n" + indent + " final int dataLength = %s%s;\n" + @@ -1200,6 +1854,7 @@ private void generateVarDataTypedDecoder( propertyName, exchangeType, generateArrayFieldNotPresentCondition(token.version(), indent), + accessOrderListenerCall, sizeOfLengthField, PrimitiveType.UINT32 == lengthType ? "(int)" : "", generateGet(lengthType, "limit", byteOrderStr)); @@ -1209,6 +1864,7 @@ private void generateDataTypedEncoder( final StringBuilder sb, final String className, final String propertyName, + final CharSequence accessOrderListenerCall, final int sizeOfLengthField, final int maxLengthValue, final String exchangeType, @@ -1225,6 +1881,7 @@ private void generateDataTypedEncoder( indent + " {\n" + indent + " throw new IllegalStateException(\"length > maxValue for type: \" + length);\n" + indent + " }\n\n" + + "%7$s" + indent + " final int headerLength = %5$d;\n" + indent + " final int limit = parentMessage.limit();\n" + indent + " parentMessage.limit(limit + headerLength + length);\n" + @@ -1237,7 +1894,8 @@ private void generateDataTypedEncoder( exchangeType, maxLengthValue, sizeOfLengthField, - generatePut(lengthPutType, "limit", "length", byteOrderStr)); + generatePut(lengthPutType, "limit", "length", byteOrderStr), + accessOrderListenerCall); } private void generateBitSet(final List tokens) throws IOException @@ -1359,26 +2017,29 @@ private void generateComposite(final List tokens) throws IOException generateEncodingOffsetMethod(sb, propertyName, encodingToken.offset(), BASE_INDENT); generateEncodingLengthMethod(sb, propertyName, encodingToken.encodedLength(), BASE_INDENT); generateFieldSinceVersionMethod(sb, encodingToken, BASE_INDENT); + final String accessOrderListenerCall = ""; switch (encodingToken.signal()) { case ENCODING: generatePrimitiveDecoder( - sb, true, encodingToken.name(), encodingToken, encodingToken, BASE_INDENT); + sb, true, encodingToken.name(), "", encodingToken, encodingToken, BASE_INDENT); break; case BEGIN_ENUM: - generateEnumDecoder(sb, true, encodingToken, propertyName, encodingToken, BASE_INDENT); + generateEnumDecoder(sb, true, "", encodingToken, propertyName, encodingToken, BASE_INDENT); break; case BEGIN_SET: generateBitSetProperty( - sb, true, DECODER, propertyName, encodingToken, encodingToken, BASE_INDENT, typeName); + sb, true, DECODER, propertyName, accessOrderListenerCall, + encodingToken, encodingToken, BASE_INDENT, typeName); break; case BEGIN_COMPOSITE: generateCompositeProperty( - sb, true, DECODER, propertyName, encodingToken, encodingToken, BASE_INDENT, typeName); + sb, true, DECODER, propertyName, accessOrderListenerCall, + encodingToken, encodingToken, BASE_INDENT, typeName); break; default: @@ -1410,25 +2071,30 @@ private void generateComposite(final List tokens) throws IOException final StringBuilder sb = new StringBuilder(); generateEncodingOffsetMethod(sb, propertyName, encodingToken.offset(), BASE_INDENT); generateEncodingLengthMethod(sb, propertyName, encodingToken.encodedLength(), BASE_INDENT); + final String accessOrderListenerCall = ""; switch (encodingToken.signal()) { case ENCODING: - generatePrimitiveEncoder(sb, encoderName, encodingToken.name(), encodingToken, BASE_INDENT); + generatePrimitiveEncoder(sb, encoderName, encodingToken.name(), + accessOrderListenerCall, encodingToken, BASE_INDENT); break; case BEGIN_ENUM: - generateEnumEncoder(sb, encoderName, encodingToken, propertyName, encodingToken, BASE_INDENT); + generateEnumEncoder(sb, encoderName, accessOrderListenerCall, + encodingToken, propertyName, encodingToken, BASE_INDENT); break; case BEGIN_SET: generateBitSetProperty( - sb, true, ENCODER, propertyName, encodingToken, encodingToken, BASE_INDENT, typeName); + sb, true, ENCODER, propertyName, accessOrderListenerCall, + encodingToken, encodingToken, BASE_INDENT, typeName); break; case BEGIN_COMPOSITE: generateCompositeProperty( - sb, true, ENCODER, propertyName, encodingToken, encodingToken, BASE_INDENT, typeName); + sb, true, ENCODER, propertyName, accessOrderListenerCall, + encodingToken, encodingToken, BASE_INDENT, typeName); break; default: @@ -1855,6 +2521,7 @@ private void generatePrimitiveDecoder( final StringBuilder sb, final boolean inComposite, final String propertyName, + final CharSequence accessOrderListenerCall, final Token propertyToken, final Token encodingToken, final String indent) @@ -1870,7 +2537,7 @@ private void generatePrimitiveDecoder( else { sb.append(generatePrimitivePropertyDecodeMethods( - inComposite, formattedPropertyName, propertyToken, encodingToken, indent)); + inComposite, formattedPropertyName, accessOrderListenerCall, propertyToken, encodingToken, indent)); } } @@ -1878,43 +2545,52 @@ private void generatePrimitiveEncoder( final StringBuilder sb, final String containingClassName, final String propertyName, - final Token token, + final CharSequence accessOrderListenerCall, + final Token typeToken, final String indent) { final String formattedPropertyName = formatPropertyName(propertyName); - generatePrimitiveFieldMetaMethod(sb, formattedPropertyName, token, indent); + generatePrimitiveFieldMetaMethod(sb, formattedPropertyName, typeToken, indent); - if (!token.isConstantEncoding()) + if (!typeToken.isConstantEncoding()) { sb.append(generatePrimitivePropertyEncodeMethods( - containingClassName, formattedPropertyName, token, indent)); + containingClassName, formattedPropertyName, accessOrderListenerCall, typeToken, indent)); } else { - generateConstPropertyMethods(sb, formattedPropertyName, token, indent); + generateConstPropertyMethods(sb, formattedPropertyName, typeToken, indent); } } private CharSequence generatePrimitivePropertyDecodeMethods( final boolean inComposite, final String propertyName, + final CharSequence accessOrderListenerCall, final Token propertyToken, final Token encodingToken, final String indent) { return encodingToken.matchOnLength( - () -> generatePrimitivePropertyDecode(inComposite, propertyName, propertyToken, encodingToken, indent), + () -> generatePrimitivePropertyDecode( + inComposite, propertyName, accessOrderListenerCall, propertyToken, encodingToken, indent), () -> generatePrimitiveArrayPropertyDecode( - inComposite, propertyName, propertyToken, encodingToken, indent)); + inComposite, propertyName, accessOrderListenerCall, propertyToken, encodingToken, indent)); } private CharSequence generatePrimitivePropertyEncodeMethods( - final String containingClassName, final String propertyName, final Token token, final String indent) + final String containingClassName, + final String propertyName, + final CharSequence accessOrderListenerCall, + final Token typeToken, + final String indent) { - return token.matchOnLength( - () -> generatePrimitivePropertyEncode(containingClassName, propertyName, token, indent), - () -> generatePrimitiveArrayPropertyEncode(containingClassName, propertyName, token, indent)); + return typeToken.matchOnLength( + () -> generatePrimitivePropertyEncode( + containingClassName, propertyName, accessOrderListenerCall, typeToken, indent), + () -> generatePrimitiveArrayPropertyEncode( + containingClassName, propertyName, accessOrderListenerCall, typeToken, indent)); } private void generatePrimitiveFieldMetaMethod( @@ -1953,6 +2629,7 @@ private void generatePrimitiveFieldMetaMethod( private CharSequence generatePrimitivePropertyDecode( final boolean inComposite, final String propertyName, + final CharSequence accessOrderListenerCall, final Token propertyToken, final Token encodingToken, final String indent) @@ -1968,32 +2645,40 @@ private CharSequence generatePrimitivePropertyDecode( indent + " public %s %s()\n" + indent + " {\n" + "%s" + + "%s" + indent + " return %s;\n" + indent + " }\n\n", javaTypeName, formatPropertyName(propertyName), generateFieldNotPresentCondition(inComposite, propertyToken.version(), encoding, indent), + accessOrderListenerCall, generateGet(encoding.primitiveType(), "offset + " + offset, byteOrderStr)); } private CharSequence generatePrimitivePropertyEncode( - final String containingClassName, final String propertyName, final Token token, final String indent) + final String containingClassName, + final String propertyName, + final CharSequence accessOrderListenerCall, + final Token typeToken, + final String indent) { - final Encoding encoding = token.encoding(); + final Encoding encoding = typeToken.encoding(); final String javaTypeName = javaTypeName(encoding.primitiveType()); - final int offset = token.offset(); + final int offset = typeToken.offset(); final String byteOrderStr = byteOrderString(encoding); return String.format( "\n" + indent + " public %s %s(final %s value)\n" + indent + " {\n" + + "%s" + indent + " %s;\n" + indent + " return this;\n" + indent + " }\n\n", formatClassName(containingClassName), formatPropertyName(propertyName), javaTypeName, + accessOrderListenerCall, generatePut(encoding.primitiveType(), "offset + " + offset, "value", byteOrderStr)); } @@ -2094,6 +2779,7 @@ private static CharSequence generatePropertyNotPresentCondition( private CharSequence generatePrimitiveArrayPropertyDecode( final boolean inComposite, final String propertyName, + final CharSequence accessOrderListenerCall, final Token propertyToken, final Token encodingToken, final String indent) @@ -2117,6 +2803,7 @@ private CharSequence generatePrimitiveArrayPropertyDecode( indent + " throw new IndexOutOfBoundsException(\"index out of range: index=\" + index);\n" + indent + " }\n\n" + "%s" + + "%s" + indent + " final int pos = offset + %d + (index * %d);\n\n" + indent + " return %s;\n" + indent + " }\n\n", @@ -2124,6 +2811,7 @@ private CharSequence generatePrimitiveArrayPropertyDecode( propertyName, fieldLength, generateFieldNotPresentCondition(inComposite, propertyToken.version(), encoding, indent), + accessOrderListenerCall, offset, typeSize, generateGet(encoding.primitiveType(), "pos", byteOrderStr)); @@ -2142,18 +2830,21 @@ private CharSequence generatePrimitiveArrayPropertyDecode( "\"Copy will go out of range: offset=\" + dstOffset);\n" + indent + " }\n\n" + "%s" + + "%s" + indent + " buffer.getBytes(offset + %d, dst, dstOffset, length);\n\n" + indent + " return length;\n" + indent + " }\n", Generators.toUpperFirstChar(propertyName), fieldLength, generateArrayFieldNotPresentCondition(propertyToken.version(), indent), + accessOrderListenerCall, offset); new Formatter(sb).format("\n" + indent + " public String %s()\n" + indent + " {\n" + "%s" + + "%s" + indent + " final byte[] dst = new byte[%d];\n" + indent + " buffer.getBytes(offset + %d, dst, 0, %d);\n\n" + indent + " int end = 0;\n" + @@ -2162,6 +2853,7 @@ private CharSequence generatePrimitiveArrayPropertyDecode( indent + " }\n\n", propertyName, generateStringNotPresentCondition(propertyToken.version(), indent), + accessOrderListenerCall, fieldLength, offset, fieldLength, @@ -2174,6 +2866,7 @@ private CharSequence generatePrimitiveArrayPropertyDecode( indent + " public int get%1$s(final Appendable value)\n" + indent + " {\n" + "%2$s" + + "%5$s" + indent + " for (int i = 0; i < %3$d; ++i)\n" + indent + " {\n" + indent + " final int c = buffer.getByte(offset + %4$d + i) & 0xFF;\n" + @@ -2195,7 +2888,8 @@ private CharSequence generatePrimitiveArrayPropertyDecode( Generators.toUpperFirstChar(propertyName), generateStringNotPresentConditionForAppendable(propertyToken.version(), indent), fieldLength, - offset); + offset, + accessOrderListenerCall); } } else if (encoding.primitiveType() == PrimitiveType.UINT8) @@ -2204,12 +2898,14 @@ else if (encoding.primitiveType() == PrimitiveType.UINT8) indent + " public int get%s(final byte[] dst, final int dstOffset, final int length)\n" + indent + " {\n" + "%s" + + "%s" + indent + " final int bytesCopied = Math.min(length, %d);\n" + indent + " buffer.getBytes(offset + %d, dst, dstOffset, bytesCopied);\n\n" + indent + " return bytesCopied;\n" + indent + " }\n", Generators.toUpperFirstChar(propertyName), generateArrayFieldNotPresentCondition(propertyToken.version(), indent), + accessOrderListenerCall, fieldLength, offset); @@ -2217,6 +2913,7 @@ else if (encoding.primitiveType() == PrimitiveType.UINT8) indent + " public int get%s(final %s dst, final int dstOffset, final int length)\n" + indent + " {\n" + "%s" + + "%s" + indent + " final int bytesCopied = Math.min(length, %d);\n" + indent + " buffer.getBytes(offset + %d, dst, dstOffset, bytesCopied);\n\n" + indent + " return bytesCopied;\n" + @@ -2224,6 +2921,7 @@ else if (encoding.primitiveType() == PrimitiveType.UINT8) Generators.toUpperFirstChar(propertyName), fqMutableBuffer, generateArrayFieldNotPresentCondition(propertyToken.version(), indent), + accessOrderListenerCall, fieldLength, offset); @@ -2231,11 +2929,13 @@ else if (encoding.primitiveType() == PrimitiveType.UINT8) indent + " public void wrap%s(final %s wrapBuffer)\n" + indent + " {\n" + "%s" + + "%s" + indent + " wrapBuffer.wrap(buffer, offset + %d, %d);\n" + indent + " }\n", Generators.toUpperFirstChar(propertyName), readOnlyBuffer, generateWrapFieldNotPresentCondition(propertyToken.version(), indent), + accessOrderListenerCall, offset, fieldLength); } @@ -2260,14 +2960,18 @@ private String byteOrderString(final Encoding encoding) } private CharSequence generatePrimitiveArrayPropertyEncode( - final String containingClassName, final String propertyName, final Token token, final String indent) + final String containingClassName, + final String propertyName, + final CharSequence accessOrderListenerCall, + final Token typeToken, + final String indent) { - final Encoding encoding = token.encoding(); + final Encoding encoding = typeToken.encoding(); final PrimitiveType primitiveType = encoding.primitiveType(); final String javaTypeName = javaTypeName(primitiveType); - final int offset = token.offset(); + final int offset = typeToken.offset(); final String byteOrderStr = byteOrderString(encoding); - final int arrayLength = token.arrayLength(); + final int arrayLength = typeToken.arrayLength(); final int typeSize = sizeOfPrimitive(encoding); final StringBuilder sb = new StringBuilder(); @@ -2282,6 +2986,7 @@ private CharSequence generatePrimitiveArrayPropertyEncode( indent + " {\n" + indent + " throw new IndexOutOfBoundsException(\"index out of range: index=\" + index);\n" + indent + " }\n\n" + + "%s" + indent + " final int pos = offset + %d + (index * %d);\n" + indent + " %s;\n\n" + indent + " return this;\n" + @@ -2290,6 +2995,7 @@ private CharSequence generatePrimitiveArrayPropertyEncode( propertyName, javaTypeName, arrayLength, + accessOrderListenerCall, offset, typeSize, generatePut(primitiveType, "pos", "value", byteOrderStr)); @@ -2310,6 +3016,8 @@ private CharSequence generatePrimitiveArrayPropertyEncode( sb.append(")\n"); sb.append(indent).append(" {\n"); + sb.append(accessOrderListenerCall); + for (int i = 0; i < arrayLength; i++) { final String indexStr = "offset + " + (offset + (typeSize * i)); @@ -2327,12 +3035,25 @@ private CharSequence generatePrimitiveArrayPropertyEncode( if (primitiveType == PrimitiveType.CHAR) { generateCharArrayEncodeMethods( - containingClassName, propertyName, indent, encoding, offset, arrayLength, sb); + containingClassName, + propertyName, + indent, + accessOrderListenerCall, + encoding, + offset, + arrayLength, + sb); } else if (primitiveType == PrimitiveType.UINT8) { generateByteArrayEncodeMethods( - containingClassName, propertyName, indent, offset, arrayLength, sb); + containingClassName, + propertyName, + indent, + accessOrderListenerCall, + offset, + arrayLength, + sb); } return sb; @@ -2342,6 +3063,7 @@ private void generateCharArrayEncodeMethods( final String containingClassName, final String propertyName, final String indent, + final CharSequence accessOrderListenerCall, final Encoding encoding, final int offset, final int fieldLength, @@ -2358,12 +3080,14 @@ private void generateCharArrayEncodeMethods( indent + " throw new IndexOutOfBoundsException(" + "\"Copy will go out of range: offset=\" + srcOffset);\n" + indent + " }\n\n" + + "%s" + indent + " buffer.putBytes(offset + %d, src, srcOffset, length);\n\n" + indent + " return this;\n" + indent + " }\n", formatClassName(containingClassName), Generators.toUpperFirstChar(propertyName), fieldLength, + accessOrderListenerCall, offset); if (isAsciiEncoding(encoding.characterEncoding())) @@ -2378,6 +3102,7 @@ private void generateCharArrayEncodeMethods( indent + " throw new IndexOutOfBoundsException(" + "\"String too large for copy: byte length=\" + srcLength);\n" + indent + " }\n\n" + + "%5$s" + indent + " buffer.putStringWithoutLengthAscii(offset + %4$d, src);\n\n" + indent + " for (int start = srcLength; start < length; ++start)\n" + indent + " {\n" + @@ -2388,7 +3113,8 @@ private void generateCharArrayEncodeMethods( formatClassName(containingClassName), propertyName, fieldLength, - offset); + offset, + accessOrderListenerCall); new Formatter(sb).format("\n" + indent + " public %1$s %2$s(final CharSequence src)\n" + @@ -2400,6 +3126,7 @@ private void generateCharArrayEncodeMethods( indent + " throw new IndexOutOfBoundsException(" + "\"CharSequence too large for copy: byte length=\" + srcLength);\n" + indent + " }\n\n" + + "%5$s" + indent + " buffer.putStringWithoutLengthAscii(offset + %4$d, src);\n\n" + indent + " for (int start = srcLength; start < length; ++start)\n" + indent + " {\n" + @@ -2410,7 +3137,8 @@ private void generateCharArrayEncodeMethods( formatClassName(containingClassName), propertyName, fieldLength, - offset); + offset, + accessOrderListenerCall); } else { @@ -2425,6 +3153,7 @@ private void generateCharArrayEncodeMethods( indent + " throw new IndexOutOfBoundsException(" + "\"String too large for copy: byte length=\" + bytes.length);\n" + indent + " }\n\n" + + "%s" + indent + " buffer.putBytes(offset + %d, bytes, 0, bytes.length);\n\n" + indent + " for (int start = bytes.length; start < length; ++start)\n" + indent + " {\n" + @@ -2436,6 +3165,7 @@ private void generateCharArrayEncodeMethods( propertyName, fieldLength, charset(encoding.characterEncoding()), + accessOrderListenerCall, offset, offset); } @@ -2445,6 +3175,7 @@ private void generateByteArrayEncodeMethods( final String containingClassName, final String propertyName, final String indent, + final CharSequence accessOrderListenerCall, final int offset, final int fieldLength, final StringBuilder sb) @@ -2457,6 +3188,7 @@ private void generateByteArrayEncodeMethods( indent + " throw new IllegalStateException(" + "\"length > maxValue for type: \" + length);\n" + indent + " }\n\n" + + "%s" + indent + " buffer.putBytes(offset + %d, src, srcOffset, length);\n" + indent + " for (int i = length; i < %d; i++)\n" + indent + " {\n" + @@ -2467,6 +3199,7 @@ private void generateByteArrayEncodeMethods( formatClassName(containingClassName), Generators.toUpperFirstChar(propertyName), fieldLength, + accessOrderListenerCall, offset, fieldLength, offset); @@ -2479,6 +3212,7 @@ private void generateByteArrayEncodeMethods( indent + " throw new IllegalStateException(" + "\"length > maxValue for type: \" + length);\n" + indent + " }\n\n" + + "%s" + indent + " buffer.putBytes(offset + %d, src, srcOffset, length);\n" + indent + " for (int i = length; i < %d; i++)\n" + indent + " {\n" + @@ -2490,6 +3224,7 @@ private void generateByteArrayEncodeMethods( Generators.toUpperFirstChar(propertyName), fqReadOnlyBuffer, fieldLength, + accessOrderListenerCall, offset, fieldLength, offset); @@ -2663,67 +3398,90 @@ private CharSequence generateFixedFlyweightCode( semanticVersion); } - private CharSequence generateDecoderFlyweightCode(final String className, final Token token) + private CharSequence generateDecoderFlyweightCode( + final FieldPrecedenceModel fieldPrecedenceModel, + final String className, + final Token token) { final String headerClassName = formatClassName(ir.headerStructure().tokens().get(0).applicableTypeName()); - final String methods = - " public " + className + " wrap(\n" + - " final " + readOnlyBuffer + " buffer,\n" + - " final int offset,\n" + - " final int actingBlockLength,\n" + - " final int actingVersion)\n" + - " {\n" + - " if (buffer != this.buffer)\n" + - " {\n" + - " this.buffer = buffer;\n" + - " }\n" + - " this.initialOffset = offset;\n" + - " this.offset = offset;\n" + - " this.actingBlockLength = actingBlockLength;\n" + - " this.actingVersion = actingVersion;\n" + - " limit(offset + actingBlockLength);\n\n" + - " return this;\n" + - " }\n\n" + + final StringBuilder methods = new StringBuilder(); - " public " + className + " wrapAndApplyHeader(\n" + - " final " + readOnlyBuffer + " buffer,\n" + - " final int offset,\n" + - " final " + headerClassName + "Decoder headerDecoder)\n" + - " {\n" + - " headerDecoder.wrap(buffer, offset);\n\n" + - " final int templateId = headerDecoder.templateId();\n" + - " if (TEMPLATE_ID != templateId)\n" + - " {\n" + - " throw new IllegalStateException(\"Invalid TEMPLATE_ID: \" + templateId);\n" + - " }\n\n" + - " return wrap(\n" + - " buffer,\n" + - " offset + " + headerClassName + "Decoder.ENCODED_LENGTH,\n" + - " headerDecoder.blockLength(),\n" + - " headerDecoder.version());\n" + - " }\n\n" + + methods.append(generateDecoderWrapListener(fieldPrecedenceModel, " ")); - " public " + className + " sbeRewind()\n" + - " {\n" + - " return wrap(buffer, initialOffset, actingBlockLength, actingVersion);\n" + - " }\n\n" + + methods.append(" public ").append(className).append(" wrap(\n") + .append(" final ").append(readOnlyBuffer).append(" buffer,\n") + .append(" final int offset,\n") + .append(" final int actingBlockLength,\n") + .append(" final int actingVersion)\n") + .append(" {\n") + .append(" if (buffer != this.buffer)\n") + .append(" {\n") + .append(" this.buffer = buffer;\n") + .append(" }\n") + .append(" this.initialOffset = offset;\n") + .append(" this.offset = offset;\n") + .append(" this.actingBlockLength = actingBlockLength;\n") + .append(" this.actingVersion = actingVersion;\n") + .append(" limit(offset + actingBlockLength);\n\n") + .append(generateAccessOrderListenerCall(fieldPrecedenceModel, " ", "onWrap", "actingVersion")) + .append(" return this;\n") + .append(" }\n\n"); - " public int sbeDecodedLength()\n" + - " {\n" + - " final int currentLimit = limit();\n" + - " sbeSkip();\n" + - " final int decodedLength = encodedLength();\n" + - " limit(currentLimit);\n\n" + - " return decodedLength;\n" + - " }\n\n" + + methods.append(" public ").append(className).append(" wrapAndApplyHeader(\n") + .append(" final ").append(readOnlyBuffer).append(" buffer,\n") + .append(" final int offset,\n") + .append(" final ").append(headerClassName).append("Decoder headerDecoder)\n") + .append(" {\n") + .append(" headerDecoder.wrap(buffer, offset);\n\n") + .append(" final int templateId = headerDecoder.templateId();\n") + .append(" if (TEMPLATE_ID != templateId)\n") + .append(" {\n") + .append(" throw new IllegalStateException(\"Invalid TEMPLATE_ID: \" + templateId);\n") + .append(" }\n\n") + .append(" return wrap(\n") + .append(" buffer,\n") + .append(" offset + ").append(headerClassName).append("Decoder.ENCODED_LENGTH,\n") + .append(" headerDecoder.blockLength(),\n") + .append(" headerDecoder.version());\n") + .append(" }\n\n"); + + methods.append(" public ").append(className).append(" sbeRewind()\n") + .append(" {\n") + .append(" return wrap(buffer, initialOffset, actingBlockLength, actingVersion);\n") + .append(" }\n\n"); - " public int actingVersion()\n" + - " {\n" + - " return actingVersion;\n" + - " }\n\n"; + methods.append(" public int sbeDecodedLength()\n") + .append(" {\n") + .append(" final int currentLimit = limit();\n"); - return generateFlyweightCode(DECODER, className, token, methods, readOnlyBuffer); + if (null != fieldPrecedenceModel) + { + methods.append(" final int currentCodecState = codecState();\n"); + } + + methods + .append(" sbeSkip();\n") + .append(" final int decodedLength = encodedLength();\n") + .append(" limit(currentLimit);\n\n"); + + if (null != fieldPrecedenceModel) + { + methods.append(" if (").append(precedenceChecksFlagName).append(")\n") + .append(" {\n") + .append(" codecState(currentCodecState);\n") + .append(" }\n\n"); + } + + methods.append(" return decodedLength;\n") + .append(" }\n\n"); + + methods.append(" public int actingVersion()\n") + .append(" {\n") + .append(" return actingVersion;\n") + .append(" }\n\n"); + + return generateFlyweightCode(DECODER, className, token, methods.toString(), readOnlyBuffer); } private CharSequence generateFlyweightCode( @@ -2829,7 +3587,10 @@ private CharSequence generateFlyweightCode( semanticVersion); } - private CharSequence generateEncoderFlyweightCode(final String className, final Token token) + private CharSequence generateEncoderFlyweightCode( + final String className, + final FieldPrecedenceModel fieldPrecedenceModel, + final Token token) { final String wrapMethod = " public " + className + " wrap(final " + mutableBuffer + " buffer, final int offset)\n" + @@ -2841,6 +3602,7 @@ private CharSequence generateEncoderFlyweightCode(final String className, final " this.initialOffset = offset;\n" + " this.offset = offset;\n" + " limit(offset + BLOCK_LENGTH);\n\n" + + generateEncoderWrapListener(fieldPrecedenceModel, " ") + " return this;\n" + " }\n\n"; @@ -2889,7 +3651,11 @@ private CharSequence generateEncoderFlyweightCode(final String className, final } private void generateEncoderFields( - final StringBuilder sb, final String containingClassName, final List tokens, final String indent) + final StringBuilder sb, + final String containingClassName, + final FieldPrecedenceModel fieldPrecedenceModel, + final List tokens, + final String indent) { Generators.forEachField( tokens, @@ -2903,25 +3669,32 @@ private void generateEncoderFields( generateEncodingOffsetMethod(sb, propertyName, fieldToken.offset(), indent); generateEncodingLengthMethod(sb, propertyName, typeToken.encodedLength(), indent); generateFieldMetaAttributeMethod(sb, fieldToken, indent); + generateAccessOrderListenerMethod(sb, fieldPrecedenceModel, indent + " ", fieldToken); + final CharSequence accessOrderListenerCall = generateAccessOrderListenerCall( + fieldPrecedenceModel, indent + " ", fieldToken); switch (typeToken.signal()) { case ENCODING: - generatePrimitiveEncoder(sb, containingClassName, propertyName, typeToken, indent); + generatePrimitiveEncoder(sb, containingClassName, propertyName, + accessOrderListenerCall, typeToken, indent); break; case BEGIN_ENUM: - generateEnumEncoder(sb, containingClassName, fieldToken, propertyName, typeToken, indent); + generateEnumEncoder(sb, containingClassName, + accessOrderListenerCall, fieldToken, propertyName, typeToken, indent); break; case BEGIN_SET: generateBitSetProperty( - sb, false, ENCODER, propertyName, fieldToken, typeToken, indent, typeName); + sb, false, ENCODER, propertyName, accessOrderListenerCall, + fieldToken, typeToken, indent, typeName); break; case BEGIN_COMPOSITE: generateCompositeProperty( - sb, false, ENCODER, propertyName, fieldToken, typeToken, indent, typeName); + sb, false, ENCODER, propertyName, accessOrderListenerCall, + fieldToken, typeToken, indent, typeName); break; default: @@ -2930,7 +3703,11 @@ private void generateEncoderFields( }); } - private void generateDecoderFields(final StringBuilder sb, final List tokens, final String indent) + private void generateDecoderFields( + final StringBuilder sb, + final FieldPrecedenceModel fieldPrecedenceModel, + final List tokens, + final String indent) { Generators.forEachField( tokens, @@ -2945,24 +3722,32 @@ private void generateDecoderFields(final StringBuilder sb, final List tok generateEncodingLengthMethod(sb, propertyName, typeToken.encodedLength(), indent); generateFieldMetaAttributeMethod(sb, fieldToken, indent); + generateAccessOrderListenerMethod(sb, fieldPrecedenceModel, indent + " ", fieldToken); + final CharSequence accessOrderListenerCall = generateAccessOrderListenerCall( + fieldPrecedenceModel, indent + " ", fieldToken); + switch (typeToken.signal()) { case ENCODING: - generatePrimitiveDecoder(sb, false, propertyName, fieldToken, typeToken, indent); + generatePrimitiveDecoder( + sb, false, propertyName, accessOrderListenerCall, fieldToken, typeToken, indent); break; case BEGIN_ENUM: - generateEnumDecoder(sb, false, fieldToken, propertyName, typeToken, indent); + generateEnumDecoder( + sb, false, accessOrderListenerCall, fieldToken, propertyName, typeToken, indent); break; case BEGIN_SET: generateBitSetProperty( - sb, false, DECODER, propertyName, fieldToken, typeToken, indent, typeName); + sb, false, DECODER, propertyName, accessOrderListenerCall, + fieldToken, typeToken, indent, typeName); break; case BEGIN_COMPOSITE: generateCompositeProperty( - sb, false, DECODER, propertyName, fieldToken, typeToken, indent, typeName); + sb, false, DECODER, propertyName, accessOrderListenerCall, + fieldToken, typeToken, indent, typeName); break; default: @@ -3063,6 +3848,7 @@ private static void generateFieldMetaAttributeMethod(final StringBuilder sb, fin private void generateEnumDecoder( final StringBuilder sb, final boolean inComposite, + final CharSequence accessOrderListenerCall, final Token fieldToken, final String propertyName, final Token typeToken, @@ -3106,11 +3892,13 @@ private void generateEnumDecoder( indent + " public %s %sRaw()\n" + indent + " {\n" + "%s" + + "%s" + indent + " return %s;\n" + indent + " }\n", javaTypeName, formatPropertyName(propertyName), generateFieldNotPresentCondition(inComposite, fieldToken.version(), encoding, indent), + accessOrderListenerCall, rawGetStr); new Formatter(sb).format( @@ -3118,11 +3906,13 @@ private void generateEnumDecoder( indent + " public %s %s()\n" + indent + " {\n" + "%s" + + "%s" + indent + " return %s.get(%s);\n" + indent + " }\n\n", enumName, propertyName, generatePropertyNotPresentCondition(inComposite, DECODER, fieldToken, enumName, indent), + accessOrderListenerCall, enumName, rawGetStr); } @@ -3131,6 +3921,7 @@ private void generateEnumDecoder( private void generateEnumEncoder( final StringBuilder sb, final String containingClassName, + final CharSequence accessOrderListenerCall, final Token fieldToken, final String propertyName, final Token typeToken, @@ -3146,12 +3937,14 @@ private void generateEnumEncoder( new Formatter(sb).format("\n" + indent + " public %s %s(final %s value)\n" + indent + " {\n" + + "%s" + indent + " %s;\n" + indent + " return this;\n" + indent + " }\n", formatClassName(containingClassName), propertyName, enumName, + accessOrderListenerCall, generatePut(encoding.primitiveType(), "offset + " + offset, "value.value()", byteOrderString)); } } @@ -3161,6 +3954,7 @@ private void generateBitSetProperty( final boolean inComposite, final CodecType codecType, final String propertyName, + final CharSequence accessOrderListenerCall, final Token propertyToken, final Token bitsetToken, final String indent, @@ -3177,12 +3971,14 @@ private void generateBitSetProperty( indent + " public %s %s()\n" + indent + " {\n" + "%s" + + "%s" + indent + " %s.wrap(buffer, offset + %d);\n" + indent + " return %s;\n" + indent + " }\n", bitSetName, propertyName, generatePropertyNotPresentCondition(inComposite, codecType, propertyToken, null, indent), + accessOrderListenerCall, propertyName, bitsetToken.offset(), propertyName); @@ -3193,6 +3989,7 @@ private void generateCompositeProperty( final boolean inComposite, final CodecType codecType, final String propertyName, + final CharSequence accessOrderListenerCall, final Token propertyToken, final Token compositeToken, final String indent, @@ -3209,12 +4006,14 @@ private void generateCompositeProperty( indent + " public %s %s()\n" + indent + " {\n" + "%s" + + "%s" + indent + " %s.wrap(buffer, offset + %d);\n" + indent + " return %s;\n" + indent + " }\n", compositeName, propertyName, generatePropertyNotPresentCondition(inComposite, codecType, propertyToken, null, indent), + accessOrderListenerCall, propertyName, compositeToken.offset(), propertyName); diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/FrameCodecDecoder.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/FrameCodecDecoder.java index 8cbdc11954..89962bd61f 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/FrameCodecDecoder.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/FrameCodecDecoder.java @@ -11,6 +11,83 @@ @SuppressWarnings("all") public final class FrameCodecDecoder { + private static final boolean ENABLE_BOUNDS_CHECKS = !Boolean.getBoolean("agrona.disable.bounds.checks"); + + private static final boolean SBE_ENABLE_IR_PRECEDENCE_CHECKS = Boolean.parseBoolean(System.getProperty( + "sbe.enable.ir.precedence.checks", + Boolean.toString(ENABLE_BOUNDS_CHECKS))); + + /** + * The states in which a encoder/decoder/codec can live. + * + *

The state machine diagram below, encoded in the dot language, describes + * the valid state transitions according to the order in which fields may be + * accessed safely. Tools such as PlantUML and Graphviz can render it. + * + *

{@code
+     *   digraph G {
+     *       NOT_WRAPPED -> V0_BLOCK [label="  wrap(version=0)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  irId(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  irVersion(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  schemaVersion(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  packageNameLength()  "];
+     *       V0_BLOCK -> V0_PACKAGENAME_DONE [label="  packageName(?)  "];
+     *       V0_PACKAGENAME_DONE -> V0_PACKAGENAME_DONE [label="  namespaceNameLength()  "];
+     *       V0_PACKAGENAME_DONE -> V0_NAMESPACENAME_DONE [label="  namespaceName(?)  "];
+     *       V0_NAMESPACENAME_DONE -> V0_NAMESPACENAME_DONE [label="  semanticVersionLength()  "];
+     *       V0_NAMESPACENAME_DONE -> V0_SEMANTICVERSION_DONE [label="  semanticVersion(?)  "];
+     *   }
+     * }
+ */ + private static class CodecStates + { + private static final int NOT_WRAPPED = 0; + private static final int V0_BLOCK = 1; + private static final int V0_PACKAGENAME_DONE = 2; + private static final int V0_NAMESPACENAME_DONE = 3; + private static final int V0_SEMANTICVERSION_DONE = 4; + + private static final String[] STATE_NAME_LOOKUP = + { + "NOT_WRAPPED", + "V0_BLOCK", + "V0_PACKAGENAME_DONE", + "V0_NAMESPACENAME_DONE", + "V0_SEMANTICVERSION_DONE", + }; + + private static final String[] STATE_TRANSITIONS_LOOKUP = + { + "\"wrap(version=0)\"", + "\"irId(?)\", \"irVersion(?)\", \"schemaVersion(?)\", \"packageNameLength()\", \"packageName(?)\"", + "\"namespaceNameLength()\", \"namespaceName(?)\"", + "\"semanticVersionLength()\", \"semanticVersion(?)\"", + "", + }; + + private static String name(final int state) + { + return STATE_NAME_LOOKUP[state]; + } + + private static String transitions(final int state) + { + return STATE_TRANSITIONS_LOOKUP[state]; + } + } + + private int codecState = CodecStates.NOT_WRAPPED; + + private int codecState() + { + return codecState; + } + + private void codecState(int newState) + { + codecState = newState; + } + public static final int BLOCK_LENGTH = 12; public static final int TEMPLATE_ID = 1; public static final int SCHEMA_ID = 1; @@ -66,6 +143,19 @@ public int offset() return offset; } + private void onWrap(final int actingVersion) + { + switch(actingVersion) + { + case 0: + codecState(CodecStates.V0_BLOCK); + break; + default: + codecState(CodecStates.V0_BLOCK); + break; + } + } + public FrameCodecDecoder wrap( final DirectBuffer buffer, final int offset, @@ -82,6 +172,11 @@ public FrameCodecDecoder wrap( this.actingVersion = actingVersion; limit(offset + actingBlockLength); + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onWrap(actingVersion); + } + return this; } @@ -113,10 +208,16 @@ public FrameCodecDecoder sbeRewind() public int sbeDecodedLength() { final int currentLimit = limit(); + final int currentCodecState = codecState(); sbeSkip(); final int decodedLength = encodedLength(); limit(currentLimit); + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + codecState(currentCodecState); + } + return decodedLength; } @@ -170,6 +271,17 @@ public static String irIdMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onIrIdAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"irId\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecDecoder#CodecStates."); + } + } + public static int irIdNullValue() { return -2147483648; @@ -187,6 +299,11 @@ public static int irIdMaxValue() public int irId() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onIrIdAccessed(); + } + return buffer.getInt(offset + 0, java.nio.ByteOrder.LITTLE_ENDIAN); } @@ -221,6 +338,17 @@ public static String irVersionMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onIrVersionAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"irVersion\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecDecoder#CodecStates."); + } + } + public static int irVersionNullValue() { return -2147483648; @@ -238,6 +366,11 @@ public static int irVersionMaxValue() public int irVersion() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onIrVersionAccessed(); + } + return buffer.getInt(offset + 4, java.nio.ByteOrder.LITTLE_ENDIAN); } @@ -272,6 +405,17 @@ public static String schemaVersionMetaAttribute(final MetaAttribute metaAttribut return ""; } + private void onSchemaVersionAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"schemaVersion\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecDecoder#CodecStates."); + } + } + public static int schemaVersionNullValue() { return -2147483648; @@ -289,6 +433,11 @@ public static int schemaVersionMaxValue() public int schemaVersion() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSchemaVersionAccessed(); + } + return buffer.getInt(offset + 8, java.nio.ByteOrder.LITTLE_ENDIAN); } @@ -323,14 +472,54 @@ public static int packageNameHeaderLength() return 2; } + void onPackageNameLengthAccessed() + { + switch (codecState()) + { + case CodecStates.V0_BLOCK: + codecState(CodecStates.V0_BLOCK); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot decode length of var data \"packageName\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecDecoder#CodecStates."); + } + } + + private void onPackageNameAccessed() + { + switch (codecState()) + { + case CodecStates.V0_BLOCK: + codecState(CodecStates.V0_PACKAGENAME_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"packageName\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecDecoder#CodecStates."); + } + } + public int packageNameLength() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPackageNameLengthAccessed(); + } + final int limit = parentMessage.limit(); return (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); } public int skipPackageName() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPackageNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -342,6 +531,11 @@ public int skipPackageName() public int getPackageName(final MutableDirectBuffer dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPackageNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -354,6 +548,11 @@ public int getPackageName(final MutableDirectBuffer dst, final int dstOffset, fi public int getPackageName(final byte[] dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPackageNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -366,6 +565,11 @@ public int getPackageName(final byte[] dst, final int dstOffset, final int lengt public void wrapPackageName(final DirectBuffer wrapBuffer) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPackageNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -375,6 +579,11 @@ public void wrapPackageName(final DirectBuffer wrapBuffer) public String packageName() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPackageNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -421,14 +630,54 @@ public static int namespaceNameHeaderLength() return 2; } + void onNamespaceNameLengthAccessed() + { + switch (codecState()) + { + case CodecStates.V0_PACKAGENAME_DONE: + codecState(CodecStates.V0_PACKAGENAME_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot decode length of var data \"namespaceName\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecDecoder#CodecStates."); + } + } + + private void onNamespaceNameAccessed() + { + switch (codecState()) + { + case CodecStates.V0_PACKAGENAME_DONE: + codecState(CodecStates.V0_NAMESPACENAME_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"namespaceName\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecDecoder#CodecStates."); + } + } + public int namespaceNameLength() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNamespaceNameLengthAccessed(); + } + final int limit = parentMessage.limit(); return (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); } public int skipNamespaceName() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNamespaceNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -440,6 +689,11 @@ public int skipNamespaceName() public int getNamespaceName(final MutableDirectBuffer dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNamespaceNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -452,6 +706,11 @@ public int getNamespaceName(final MutableDirectBuffer dst, final int dstOffset, public int getNamespaceName(final byte[] dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNamespaceNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -464,6 +723,11 @@ public int getNamespaceName(final byte[] dst, final int dstOffset, final int len public void wrapNamespaceName(final DirectBuffer wrapBuffer) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNamespaceNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -473,6 +737,11 @@ public void wrapNamespaceName(final DirectBuffer wrapBuffer) public String namespaceName() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNamespaceNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -519,14 +788,54 @@ public static int semanticVersionHeaderLength() return 2; } + void onSemanticVersionLengthAccessed() + { + switch (codecState()) + { + case CodecStates.V0_NAMESPACENAME_DONE: + codecState(CodecStates.V0_NAMESPACENAME_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot decode length of var data \"semanticVersion\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecDecoder#CodecStates."); + } + } + + private void onSemanticVersionAccessed() + { + switch (codecState()) + { + case CodecStates.V0_NAMESPACENAME_DONE: + codecState(CodecStates.V0_SEMANTICVERSION_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"semanticVersion\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecDecoder#CodecStates."); + } + } + public int semanticVersionLength() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticVersionLengthAccessed(); + } + final int limit = parentMessage.limit(); return (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); } public int skipSemanticVersion() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticVersionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -538,6 +847,11 @@ public int skipSemanticVersion() public int getSemanticVersion(final MutableDirectBuffer dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticVersionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -550,6 +864,11 @@ public int getSemanticVersion(final MutableDirectBuffer dst, final int dstOffset public int getSemanticVersion(final byte[] dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticVersionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -562,6 +881,11 @@ public int getSemanticVersion(final byte[] dst, final int dstOffset, final int l public void wrapSemanticVersion(final DirectBuffer wrapBuffer) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticVersionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -571,6 +895,11 @@ public void wrapSemanticVersion(final DirectBuffer wrapBuffer) public String semanticVersion() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticVersionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/FrameCodecEncoder.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/FrameCodecEncoder.java index 0fd115d556..a5524033fc 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/FrameCodecEncoder.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/FrameCodecEncoder.java @@ -11,6 +11,83 @@ @SuppressWarnings("all") public final class FrameCodecEncoder { + private static final boolean ENABLE_BOUNDS_CHECKS = !Boolean.getBoolean("agrona.disable.bounds.checks"); + + private static final boolean SBE_ENABLE_IR_PRECEDENCE_CHECKS = Boolean.parseBoolean(System.getProperty( + "sbe.enable.ir.precedence.checks", + Boolean.toString(ENABLE_BOUNDS_CHECKS))); + + /** + * The states in which a encoder/decoder/codec can live. + * + *

The state machine diagram below, encoded in the dot language, describes + * the valid state transitions according to the order in which fields may be + * accessed safely. Tools such as PlantUML and Graphviz can render it. + * + *

{@code
+     *   digraph G {
+     *       NOT_WRAPPED -> V0_BLOCK [label="  wrap(version=0)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  irId(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  irVersion(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  schemaVersion(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  packageNameLength()  "];
+     *       V0_BLOCK -> V0_PACKAGENAME_DONE [label="  packageName(?)  "];
+     *       V0_PACKAGENAME_DONE -> V0_PACKAGENAME_DONE [label="  namespaceNameLength()  "];
+     *       V0_PACKAGENAME_DONE -> V0_NAMESPACENAME_DONE [label="  namespaceName(?)  "];
+     *       V0_NAMESPACENAME_DONE -> V0_NAMESPACENAME_DONE [label="  semanticVersionLength()  "];
+     *       V0_NAMESPACENAME_DONE -> V0_SEMANTICVERSION_DONE [label="  semanticVersion(?)  "];
+     *   }
+     * }
+ */ + private static class CodecStates + { + private static final int NOT_WRAPPED = 0; + private static final int V0_BLOCK = 1; + private static final int V0_PACKAGENAME_DONE = 2; + private static final int V0_NAMESPACENAME_DONE = 3; + private static final int V0_SEMANTICVERSION_DONE = 4; + + private static final String[] STATE_NAME_LOOKUP = + { + "NOT_WRAPPED", + "V0_BLOCK", + "V0_PACKAGENAME_DONE", + "V0_NAMESPACENAME_DONE", + "V0_SEMANTICVERSION_DONE", + }; + + private static final String[] STATE_TRANSITIONS_LOOKUP = + { + "\"wrap(version=0)\"", + "\"irId(?)\", \"irVersion(?)\", \"schemaVersion(?)\", \"packageNameLength()\", \"packageName(?)\"", + "\"namespaceNameLength()\", \"namespaceName(?)\"", + "\"semanticVersionLength()\", \"semanticVersion(?)\"", + "", + }; + + private static String name(final int state) + { + return STATE_NAME_LOOKUP[state]; + } + + private static String transitions(final int state) + { + return STATE_TRANSITIONS_LOOKUP[state]; + } + } + + private int codecState = CodecStates.NOT_WRAPPED; + + private int codecState() + { + return codecState; + } + + private void codecState(int newState) + { + codecState = newState; + } + public static final int BLOCK_LENGTH = 12; public static final int TEMPLATE_ID = 1; public static final int SCHEMA_ID = 1; @@ -74,6 +151,11 @@ public FrameCodecEncoder wrap(final MutableDirectBuffer buffer, final int offset this.offset = offset; limit(offset + BLOCK_LENGTH); + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + codecState(CodecStates.V0_BLOCK); + } + return this; } @@ -135,6 +217,17 @@ public static String irIdMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onIrIdAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"irId\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecEncoder#CodecStates."); + } + } + public static int irIdNullValue() { return -2147483648; @@ -152,6 +245,11 @@ public static int irIdMaxValue() public FrameCodecEncoder irId(final int value) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onIrIdAccessed(); + } + buffer.putInt(offset + 0, value, java.nio.ByteOrder.LITTLE_ENDIAN); return this; } @@ -187,6 +285,17 @@ public static String irVersionMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onIrVersionAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"irVersion\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecEncoder#CodecStates."); + } + } + public static int irVersionNullValue() { return -2147483648; @@ -204,6 +313,11 @@ public static int irVersionMaxValue() public FrameCodecEncoder irVersion(final int value) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onIrVersionAccessed(); + } + buffer.putInt(offset + 4, value, java.nio.ByteOrder.LITTLE_ENDIAN); return this; } @@ -239,6 +353,17 @@ public static String schemaVersionMetaAttribute(final MetaAttribute metaAttribut return ""; } + private void onSchemaVersionAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"schemaVersion\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecEncoder#CodecStates."); + } + } + public static int schemaVersionNullValue() { return -2147483648; @@ -256,6 +381,11 @@ public static int schemaVersionMaxValue() public FrameCodecEncoder schemaVersion(final int value) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSchemaVersionAccessed(); + } + buffer.putInt(offset + 8, value, java.nio.ByteOrder.LITTLE_ENDIAN); return this; } @@ -286,6 +416,21 @@ public static int packageNameHeaderLength() return 2; } + private void onPackageNameAccessed() + { + switch (codecState()) + { + case CodecStates.V0_BLOCK: + codecState(CodecStates.V0_PACKAGENAME_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"packageName\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecEncoder#CodecStates."); + } + } + public FrameCodecEncoder putPackageName(final DirectBuffer src, final int srcOffset, final int length) { if (length > 65534) @@ -293,6 +438,11 @@ public FrameCodecEncoder putPackageName(final DirectBuffer src, final int srcOff throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPackageNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -309,6 +459,11 @@ public FrameCodecEncoder putPackageName(final byte[] src, final int srcOffset, f throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPackageNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -328,6 +483,11 @@ public FrameCodecEncoder packageName(final String value) throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPackageNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -362,6 +522,21 @@ public static int namespaceNameHeaderLength() return 2; } + private void onNamespaceNameAccessed() + { + switch (codecState()) + { + case CodecStates.V0_PACKAGENAME_DONE: + codecState(CodecStates.V0_NAMESPACENAME_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"namespaceName\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecEncoder#CodecStates."); + } + } + public FrameCodecEncoder putNamespaceName(final DirectBuffer src, final int srcOffset, final int length) { if (length > 65534) @@ -369,6 +544,11 @@ public FrameCodecEncoder putNamespaceName(final DirectBuffer src, final int srcO throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNamespaceNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -385,6 +565,11 @@ public FrameCodecEncoder putNamespaceName(final byte[] src, final int srcOffset, throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNamespaceNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -404,6 +589,11 @@ public FrameCodecEncoder namespaceName(final String value) throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNamespaceNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -438,6 +628,21 @@ public static int semanticVersionHeaderLength() return 2; } + private void onSemanticVersionAccessed() + { + switch (codecState()) + { + case CodecStates.V0_NAMESPACENAME_DONE: + codecState(CodecStates.V0_SEMANTICVERSION_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"semanticVersion\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class FrameCodecEncoder#CodecStates."); + } + } + public FrameCodecEncoder putSemanticVersion(final DirectBuffer src, final int srcOffset, final int length) { if (length > 65534) @@ -445,6 +650,11 @@ public FrameCodecEncoder putSemanticVersion(final DirectBuffer src, final int sr throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticVersionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -461,6 +671,11 @@ public FrameCodecEncoder putSemanticVersion(final byte[] src, final int srcOffse throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticVersionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -480,6 +695,11 @@ public FrameCodecEncoder semanticVersion(final String value) throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticVersionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -511,4 +731,21 @@ public StringBuilder appendTo(final StringBuilder builder) return decoder.appendTo(builder); } + + public void checkEncodingIsComplete() + { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + switch (codecState) + { + case CodecStates.V0_SEMANTICVERSION_DONE: + return; + default: + throw new IllegalStateException("Not fully encoded, current state: " + + CodecStates.name(codecState) + ", allowed transitions: " + + CodecStates.transitions(codecState)); + } + } + } + } diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/TokenCodecDecoder.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/TokenCodecDecoder.java index 8c7a7a1ee6..6a48488936 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/TokenCodecDecoder.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/TokenCodecDecoder.java @@ -11,6 +11,130 @@ @SuppressWarnings("all") public final class TokenCodecDecoder { + private static final boolean ENABLE_BOUNDS_CHECKS = !Boolean.getBoolean("agrona.disable.bounds.checks"); + + private static final boolean SBE_ENABLE_IR_PRECEDENCE_CHECKS = Boolean.parseBoolean(System.getProperty( + "sbe.enable.ir.precedence.checks", + Boolean.toString(ENABLE_BOUNDS_CHECKS))); + + /** + * The states in which a encoder/decoder/codec can live. + * + *

The state machine diagram below, encoded in the dot language, describes + * the valid state transitions according to the order in which fields may be + * accessed safely. Tools such as PlantUML and Graphviz can render it. + * + *

{@code
+     *   digraph G {
+     *       NOT_WRAPPED -> V0_BLOCK [label="  wrap(version=0)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  tokenOffset(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  tokenSize(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  fieldId(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  tokenVersion(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  componentTokenCount(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  signal(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  primitiveType(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  byteOrder(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  presence(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  deprecated(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  nameLength()  "];
+     *       V0_BLOCK -> V0_NAME_DONE [label="  name(?)  "];
+     *       V0_NAME_DONE -> V0_NAME_DONE [label="  constValueLength()  "];
+     *       V0_NAME_DONE -> V0_CONSTVALUE_DONE [label="  constValue(?)  "];
+     *       V0_CONSTVALUE_DONE -> V0_CONSTVALUE_DONE [label="  minValueLength()  "];
+     *       V0_CONSTVALUE_DONE -> V0_MINVALUE_DONE [label="  minValue(?)  "];
+     *       V0_MINVALUE_DONE -> V0_MINVALUE_DONE [label="  maxValueLength()  "];
+     *       V0_MINVALUE_DONE -> V0_MAXVALUE_DONE [label="  maxValue(?)  "];
+     *       V0_MAXVALUE_DONE -> V0_MAXVALUE_DONE [label="  nullValueLength()  "];
+     *       V0_MAXVALUE_DONE -> V0_NULLVALUE_DONE [label="  nullValue(?)  "];
+     *       V0_NULLVALUE_DONE -> V0_NULLVALUE_DONE [label="  characterEncodingLength()  "];
+     *       V0_NULLVALUE_DONE -> V0_CHARACTERENCODING_DONE [label="  characterEncoding(?)  "];
+     *       V0_CHARACTERENCODING_DONE -> V0_CHARACTERENCODING_DONE [label="  epochLength()  "];
+     *       V0_CHARACTERENCODING_DONE -> V0_EPOCH_DONE [label="  epoch(?)  "];
+     *       V0_EPOCH_DONE -> V0_EPOCH_DONE [label="  timeUnitLength()  "];
+     *       V0_EPOCH_DONE -> V0_TIMEUNIT_DONE [label="  timeUnit(?)  "];
+     *       V0_TIMEUNIT_DONE -> V0_TIMEUNIT_DONE [label="  semanticTypeLength()  "];
+     *       V0_TIMEUNIT_DONE -> V0_SEMANTICTYPE_DONE [label="  semanticType(?)  "];
+     *       V0_SEMANTICTYPE_DONE -> V0_SEMANTICTYPE_DONE [label="  descriptionLength()  "];
+     *       V0_SEMANTICTYPE_DONE -> V0_DESCRIPTION_DONE [label="  description(?)  "];
+     *       V0_DESCRIPTION_DONE -> V0_DESCRIPTION_DONE [label="  referencedNameLength()  "];
+     *       V0_DESCRIPTION_DONE -> V0_REFERENCEDNAME_DONE [label="  referencedName(?)  "];
+     *   }
+     * }
+ */ + private static class CodecStates + { + private static final int NOT_WRAPPED = 0; + private static final int V0_BLOCK = 1; + private static final int V0_NAME_DONE = 2; + private static final int V0_CONSTVALUE_DONE = 3; + private static final int V0_MINVALUE_DONE = 4; + private static final int V0_MAXVALUE_DONE = 5; + private static final int V0_NULLVALUE_DONE = 6; + private static final int V0_CHARACTERENCODING_DONE = 7; + private static final int V0_EPOCH_DONE = 8; + private static final int V0_TIMEUNIT_DONE = 9; + private static final int V0_SEMANTICTYPE_DONE = 10; + private static final int V0_DESCRIPTION_DONE = 11; + private static final int V0_REFERENCEDNAME_DONE = 12; + + private static final String[] STATE_NAME_LOOKUP = + { + "NOT_WRAPPED", + "V0_BLOCK", + "V0_NAME_DONE", + "V0_CONSTVALUE_DONE", + "V0_MINVALUE_DONE", + "V0_MAXVALUE_DONE", + "V0_NULLVALUE_DONE", + "V0_CHARACTERENCODING_DONE", + "V0_EPOCH_DONE", + "V0_TIMEUNIT_DONE", + "V0_SEMANTICTYPE_DONE", + "V0_DESCRIPTION_DONE", + "V0_REFERENCEDNAME_DONE", + }; + + private static final String[] STATE_TRANSITIONS_LOOKUP = + { + "\"wrap(version=0)\"", + "\"tokenOffset(?)\", \"tokenSize(?)\", \"fieldId(?)\", \"tokenVersion(?)\", \"componentTokenCount(?)\", \"signal(?)\", \"primitiveType(?)\", \"byteOrder(?)\", \"presence(?)\", \"deprecated(?)\", \"nameLength()\", \"name(?)\"", + "\"constValueLength()\", \"constValue(?)\"", + "\"minValueLength()\", \"minValue(?)\"", + "\"maxValueLength()\", \"maxValue(?)\"", + "\"nullValueLength()\", \"nullValue(?)\"", + "\"characterEncodingLength()\", \"characterEncoding(?)\"", + "\"epochLength()\", \"epoch(?)\"", + "\"timeUnitLength()\", \"timeUnit(?)\"", + "\"semanticTypeLength()\", \"semanticType(?)\"", + "\"descriptionLength()\", \"description(?)\"", + "\"referencedNameLength()\", \"referencedName(?)\"", + "", + }; + + private static String name(final int state) + { + return STATE_NAME_LOOKUP[state]; + } + + private static String transitions(final int state) + { + return STATE_TRANSITIONS_LOOKUP[state]; + } + } + + private int codecState = CodecStates.NOT_WRAPPED; + + private int codecState() + { + return codecState; + } + + private void codecState(int newState) + { + codecState = newState; + } + public static final int BLOCK_LENGTH = 28; public static final int TEMPLATE_ID = 2; public static final int SCHEMA_ID = 1; @@ -66,6 +190,19 @@ public int offset() return offset; } + private void onWrap(final int actingVersion) + { + switch(actingVersion) + { + case 0: + codecState(CodecStates.V0_BLOCK); + break; + default: + codecState(CodecStates.V0_BLOCK); + break; + } + } + public TokenCodecDecoder wrap( final DirectBuffer buffer, final int offset, @@ -82,6 +219,11 @@ public TokenCodecDecoder wrap( this.actingVersion = actingVersion; limit(offset + actingBlockLength); + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onWrap(actingVersion); + } + return this; } @@ -113,10 +255,16 @@ public TokenCodecDecoder sbeRewind() public int sbeDecodedLength() { final int currentLimit = limit(); + final int currentCodecState = codecState(); sbeSkip(); final int decodedLength = encodedLength(); limit(currentLimit); + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + codecState(currentCodecState); + } + return decodedLength; } @@ -170,6 +318,17 @@ public static String tokenOffsetMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onTokenOffsetAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"tokenOffset\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public static int tokenOffsetNullValue() { return -2147483648; @@ -187,6 +346,11 @@ public static int tokenOffsetMaxValue() public int tokenOffset() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTokenOffsetAccessed(); + } + return buffer.getInt(offset + 0, java.nio.ByteOrder.LITTLE_ENDIAN); } @@ -221,6 +385,17 @@ public static String tokenSizeMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onTokenSizeAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"tokenSize\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public static int tokenSizeNullValue() { return -2147483648; @@ -238,6 +413,11 @@ public static int tokenSizeMaxValue() public int tokenSize() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTokenSizeAccessed(); + } + return buffer.getInt(offset + 4, java.nio.ByteOrder.LITTLE_ENDIAN); } @@ -272,6 +452,17 @@ public static String fieldIdMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onFieldIdAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"fieldId\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public static int fieldIdNullValue() { return -2147483648; @@ -289,6 +480,11 @@ public static int fieldIdMaxValue() public int fieldId() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onFieldIdAccessed(); + } + return buffer.getInt(offset + 8, java.nio.ByteOrder.LITTLE_ENDIAN); } @@ -323,6 +519,17 @@ public static String tokenVersionMetaAttribute(final MetaAttribute metaAttribute return ""; } + private void onTokenVersionAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"tokenVersion\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public static int tokenVersionNullValue() { return -2147483648; @@ -340,6 +547,11 @@ public static int tokenVersionMaxValue() public int tokenVersion() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTokenVersionAccessed(); + } + return buffer.getInt(offset + 12, java.nio.ByteOrder.LITTLE_ENDIAN); } @@ -374,6 +586,17 @@ public static String componentTokenCountMetaAttribute(final MetaAttribute metaAt return ""; } + private void onComponentTokenCountAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"componentTokenCount\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public static int componentTokenCountNullValue() { return -2147483648; @@ -391,6 +614,11 @@ public static int componentTokenCountMaxValue() public int componentTokenCount() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onComponentTokenCountAccessed(); + } + return buffer.getInt(offset + 16, java.nio.ByteOrder.LITTLE_ENDIAN); } @@ -425,13 +653,34 @@ public static String signalMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onSignalAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"signal\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public short signalRaw() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSignalAccessed(); + } + return ((short)(buffer.getByte(offset + 20) & 0xFF)); } public SignalCodec signal() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSignalAccessed(); + } + return SignalCodec.get(((short)(buffer.getByte(offset + 20) & 0xFF))); } @@ -466,13 +715,34 @@ public static String primitiveTypeMetaAttribute(final MetaAttribute metaAttribut return ""; } + private void onPrimitiveTypeAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"primitiveType\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public short primitiveTypeRaw() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPrimitiveTypeAccessed(); + } + return ((short)(buffer.getByte(offset + 21) & 0xFF)); } public PrimitiveTypeCodec primitiveType() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPrimitiveTypeAccessed(); + } + return PrimitiveTypeCodec.get(((short)(buffer.getByte(offset + 21) & 0xFF))); } @@ -507,13 +777,34 @@ public static String byteOrderMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onByteOrderAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"byteOrder\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public short byteOrderRaw() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onByteOrderAccessed(); + } + return ((short)(buffer.getByte(offset + 22) & 0xFF)); } public ByteOrderCodec byteOrder() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onByteOrderAccessed(); + } + return ByteOrderCodec.get(((short)(buffer.getByte(offset + 22) & 0xFF))); } @@ -548,13 +839,34 @@ public static String presenceMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onPresenceAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"presence\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public short presenceRaw() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPresenceAccessed(); + } + return ((short)(buffer.getByte(offset + 23) & 0xFF)); } public PresenceCodec presence() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPresenceAccessed(); + } + return PresenceCodec.get(((short)(buffer.getByte(offset + 23) & 0xFF))); } @@ -589,6 +901,17 @@ public static String deprecatedMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onDeprecatedAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"deprecated\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public static int deprecatedNullValue() { return 0; @@ -606,6 +929,11 @@ public static int deprecatedMaxValue() public int deprecated() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onDeprecatedAccessed(); + } + return buffer.getInt(offset + 24, java.nio.ByteOrder.LITTLE_ENDIAN); } @@ -640,14 +968,54 @@ public static int nameHeaderLength() return 2; } + void onNameLengthAccessed() + { + switch (codecState()) + { + case CodecStates.V0_BLOCK: + codecState(CodecStates.V0_BLOCK); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot decode length of var data \"name\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + + private void onNameAccessed() + { + switch (codecState()) + { + case CodecStates.V0_BLOCK: + codecState(CodecStates.V0_NAME_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"name\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public int nameLength() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNameLengthAccessed(); + } + final int limit = parentMessage.limit(); return (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); } public int skipName() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -659,6 +1027,11 @@ public int skipName() public int getName(final MutableDirectBuffer dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -671,6 +1044,11 @@ public int getName(final MutableDirectBuffer dst, final int dstOffset, final int public int getName(final byte[] dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -683,6 +1061,11 @@ public int getName(final byte[] dst, final int dstOffset, final int length) public void wrapName(final DirectBuffer wrapBuffer) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -692,6 +1075,11 @@ public void wrapName(final DirectBuffer wrapBuffer) public String name() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -738,14 +1126,54 @@ public static int constValueHeaderLength() return 2; } + void onConstValueLengthAccessed() + { + switch (codecState()) + { + case CodecStates.V0_NAME_DONE: + codecState(CodecStates.V0_NAME_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot decode length of var data \"constValue\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + + private void onConstValueAccessed() + { + switch (codecState()) + { + case CodecStates.V0_NAME_DONE: + codecState(CodecStates.V0_CONSTVALUE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"constValue\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public int constValueLength() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onConstValueLengthAccessed(); + } + final int limit = parentMessage.limit(); return (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); } public int skipConstValue() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onConstValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -757,6 +1185,11 @@ public int skipConstValue() public int getConstValue(final MutableDirectBuffer dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onConstValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -769,6 +1202,11 @@ public int getConstValue(final MutableDirectBuffer dst, final int dstOffset, fin public int getConstValue(final byte[] dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onConstValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -781,6 +1219,11 @@ public int getConstValue(final byte[] dst, final int dstOffset, final int length public void wrapConstValue(final DirectBuffer wrapBuffer) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onConstValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -790,6 +1233,11 @@ public void wrapConstValue(final DirectBuffer wrapBuffer) public String constValue() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onConstValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -836,14 +1284,54 @@ public static int minValueHeaderLength() return 2; } + void onMinValueLengthAccessed() + { + switch (codecState()) + { + case CodecStates.V0_CONSTVALUE_DONE: + codecState(CodecStates.V0_CONSTVALUE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot decode length of var data \"minValue\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + + private void onMinValueAccessed() + { + switch (codecState()) + { + case CodecStates.V0_CONSTVALUE_DONE: + codecState(CodecStates.V0_MINVALUE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"minValue\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public int minValueLength() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMinValueLengthAccessed(); + } + final int limit = parentMessage.limit(); return (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); } public int skipMinValue() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMinValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -855,6 +1343,11 @@ public int skipMinValue() public int getMinValue(final MutableDirectBuffer dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMinValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -867,6 +1360,11 @@ public int getMinValue(final MutableDirectBuffer dst, final int dstOffset, final public int getMinValue(final byte[] dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMinValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -879,6 +1377,11 @@ public int getMinValue(final byte[] dst, final int dstOffset, final int length) public void wrapMinValue(final DirectBuffer wrapBuffer) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMinValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -888,6 +1391,11 @@ public void wrapMinValue(final DirectBuffer wrapBuffer) public String minValue() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMinValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -934,14 +1442,54 @@ public static int maxValueHeaderLength() return 2; } + void onMaxValueLengthAccessed() + { + switch (codecState()) + { + case CodecStates.V0_MINVALUE_DONE: + codecState(CodecStates.V0_MINVALUE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot decode length of var data \"maxValue\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + + private void onMaxValueAccessed() + { + switch (codecState()) + { + case CodecStates.V0_MINVALUE_DONE: + codecState(CodecStates.V0_MAXVALUE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"maxValue\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public int maxValueLength() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMaxValueLengthAccessed(); + } + final int limit = parentMessage.limit(); return (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); } public int skipMaxValue() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMaxValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -953,6 +1501,11 @@ public int skipMaxValue() public int getMaxValue(final MutableDirectBuffer dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMaxValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -965,6 +1518,11 @@ public int getMaxValue(final MutableDirectBuffer dst, final int dstOffset, final public int getMaxValue(final byte[] dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMaxValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -977,6 +1535,11 @@ public int getMaxValue(final byte[] dst, final int dstOffset, final int length) public void wrapMaxValue(final DirectBuffer wrapBuffer) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMaxValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -986,6 +1549,11 @@ public void wrapMaxValue(final DirectBuffer wrapBuffer) public String maxValue() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMaxValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1032,14 +1600,54 @@ public static int nullValueHeaderLength() return 2; } + void onNullValueLengthAccessed() + { + switch (codecState()) + { + case CodecStates.V0_MAXVALUE_DONE: + codecState(CodecStates.V0_MAXVALUE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot decode length of var data \"nullValue\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + + private void onNullValueAccessed() + { + switch (codecState()) + { + case CodecStates.V0_MAXVALUE_DONE: + codecState(CodecStates.V0_NULLVALUE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"nullValue\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public int nullValueLength() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNullValueLengthAccessed(); + } + final int limit = parentMessage.limit(); return (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); } public int skipNullValue() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNullValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1051,6 +1659,11 @@ public int skipNullValue() public int getNullValue(final MutableDirectBuffer dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNullValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1063,6 +1676,11 @@ public int getNullValue(final MutableDirectBuffer dst, final int dstOffset, fina public int getNullValue(final byte[] dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNullValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1075,6 +1693,11 @@ public int getNullValue(final byte[] dst, final int dstOffset, final int length) public void wrapNullValue(final DirectBuffer wrapBuffer) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNullValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1084,6 +1707,11 @@ public void wrapNullValue(final DirectBuffer wrapBuffer) public String nullValue() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNullValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1130,14 +1758,54 @@ public static int characterEncodingHeaderLength() return 2; } + void onCharacterEncodingLengthAccessed() + { + switch (codecState()) + { + case CodecStates.V0_NULLVALUE_DONE: + codecState(CodecStates.V0_NULLVALUE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot decode length of var data \"characterEncoding\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + + private void onCharacterEncodingAccessed() + { + switch (codecState()) + { + case CodecStates.V0_NULLVALUE_DONE: + codecState(CodecStates.V0_CHARACTERENCODING_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"characterEncoding\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public int characterEncodingLength() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onCharacterEncodingLengthAccessed(); + } + final int limit = parentMessage.limit(); return (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); } public int skipCharacterEncoding() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onCharacterEncodingAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1149,6 +1817,11 @@ public int skipCharacterEncoding() public int getCharacterEncoding(final MutableDirectBuffer dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onCharacterEncodingAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1161,6 +1834,11 @@ public int getCharacterEncoding(final MutableDirectBuffer dst, final int dstOffs public int getCharacterEncoding(final byte[] dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onCharacterEncodingAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1173,6 +1851,11 @@ public int getCharacterEncoding(final byte[] dst, final int dstOffset, final int public void wrapCharacterEncoding(final DirectBuffer wrapBuffer) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onCharacterEncodingAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1182,6 +1865,11 @@ public void wrapCharacterEncoding(final DirectBuffer wrapBuffer) public String characterEncoding() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onCharacterEncodingAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1228,14 +1916,54 @@ public static int epochHeaderLength() return 2; } + void onEpochLengthAccessed() + { + switch (codecState()) + { + case CodecStates.V0_CHARACTERENCODING_DONE: + codecState(CodecStates.V0_CHARACTERENCODING_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot decode length of var data \"epoch\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + + private void onEpochAccessed() + { + switch (codecState()) + { + case CodecStates.V0_CHARACTERENCODING_DONE: + codecState(CodecStates.V0_EPOCH_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"epoch\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public int epochLength() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onEpochLengthAccessed(); + } + final int limit = parentMessage.limit(); return (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); } public int skipEpoch() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onEpochAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1247,6 +1975,11 @@ public int skipEpoch() public int getEpoch(final MutableDirectBuffer dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onEpochAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1259,6 +1992,11 @@ public int getEpoch(final MutableDirectBuffer dst, final int dstOffset, final in public int getEpoch(final byte[] dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onEpochAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1271,6 +2009,11 @@ public int getEpoch(final byte[] dst, final int dstOffset, final int length) public void wrapEpoch(final DirectBuffer wrapBuffer) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onEpochAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1280,6 +2023,11 @@ public void wrapEpoch(final DirectBuffer wrapBuffer) public String epoch() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onEpochAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1326,14 +2074,54 @@ public static int timeUnitHeaderLength() return 2; } + void onTimeUnitLengthAccessed() + { + switch (codecState()) + { + case CodecStates.V0_EPOCH_DONE: + codecState(CodecStates.V0_EPOCH_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot decode length of var data \"timeUnit\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + + private void onTimeUnitAccessed() + { + switch (codecState()) + { + case CodecStates.V0_EPOCH_DONE: + codecState(CodecStates.V0_TIMEUNIT_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"timeUnit\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public int timeUnitLength() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTimeUnitLengthAccessed(); + } + final int limit = parentMessage.limit(); return (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); } public int skipTimeUnit() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTimeUnitAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1345,6 +2133,11 @@ public int skipTimeUnit() public int getTimeUnit(final MutableDirectBuffer dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTimeUnitAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1357,6 +2150,11 @@ public int getTimeUnit(final MutableDirectBuffer dst, final int dstOffset, final public int getTimeUnit(final byte[] dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTimeUnitAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1369,6 +2167,11 @@ public int getTimeUnit(final byte[] dst, final int dstOffset, final int length) public void wrapTimeUnit(final DirectBuffer wrapBuffer) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTimeUnitAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1378,6 +2181,11 @@ public void wrapTimeUnit(final DirectBuffer wrapBuffer) public String timeUnit() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTimeUnitAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1424,14 +2232,54 @@ public static int semanticTypeHeaderLength() return 2; } + void onSemanticTypeLengthAccessed() + { + switch (codecState()) + { + case CodecStates.V0_TIMEUNIT_DONE: + codecState(CodecStates.V0_TIMEUNIT_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot decode length of var data \"semanticType\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + + private void onSemanticTypeAccessed() + { + switch (codecState()) + { + case CodecStates.V0_TIMEUNIT_DONE: + codecState(CodecStates.V0_SEMANTICTYPE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"semanticType\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public int semanticTypeLength() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticTypeLengthAccessed(); + } + final int limit = parentMessage.limit(); return (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); } public int skipSemanticType() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticTypeAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1443,6 +2291,11 @@ public int skipSemanticType() public int getSemanticType(final MutableDirectBuffer dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticTypeAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1455,6 +2308,11 @@ public int getSemanticType(final MutableDirectBuffer dst, final int dstOffset, f public int getSemanticType(final byte[] dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticTypeAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1467,6 +2325,11 @@ public int getSemanticType(final byte[] dst, final int dstOffset, final int leng public void wrapSemanticType(final DirectBuffer wrapBuffer) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticTypeAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1476,6 +2339,11 @@ public void wrapSemanticType(final DirectBuffer wrapBuffer) public String semanticType() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticTypeAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1522,14 +2390,54 @@ public static int descriptionHeaderLength() return 2; } + void onDescriptionLengthAccessed() + { + switch (codecState()) + { + case CodecStates.V0_SEMANTICTYPE_DONE: + codecState(CodecStates.V0_SEMANTICTYPE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot decode length of var data \"description\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + + private void onDescriptionAccessed() + { + switch (codecState()) + { + case CodecStates.V0_SEMANTICTYPE_DONE: + codecState(CodecStates.V0_DESCRIPTION_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"description\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public int descriptionLength() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onDescriptionLengthAccessed(); + } + final int limit = parentMessage.limit(); return (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); } public int skipDescription() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onDescriptionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1541,6 +2449,11 @@ public int skipDescription() public int getDescription(final MutableDirectBuffer dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onDescriptionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1553,6 +2466,11 @@ public int getDescription(final MutableDirectBuffer dst, final int dstOffset, fi public int getDescription(final byte[] dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onDescriptionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1565,6 +2483,11 @@ public int getDescription(final byte[] dst, final int dstOffset, final int lengt public void wrapDescription(final DirectBuffer wrapBuffer) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onDescriptionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1574,6 +2497,11 @@ public void wrapDescription(final DirectBuffer wrapBuffer) public String description() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onDescriptionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1620,14 +2548,54 @@ public static int referencedNameHeaderLength() return 2; } + void onReferencedNameLengthAccessed() + { + switch (codecState()) + { + case CodecStates.V0_DESCRIPTION_DONE: + codecState(CodecStates.V0_DESCRIPTION_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot decode length of var data \"referencedName\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + + private void onReferencedNameAccessed() + { + switch (codecState()) + { + case CodecStates.V0_DESCRIPTION_DONE: + codecState(CodecStates.V0_REFERENCEDNAME_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"referencedName\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecDecoder#CodecStates."); + } + } + public int referencedNameLength() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onReferencedNameLengthAccessed(); + } + final int limit = parentMessage.limit(); return (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); } public int skipReferencedName() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onReferencedNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1639,6 +2607,11 @@ public int skipReferencedName() public int getReferencedName(final MutableDirectBuffer dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onReferencedNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1651,6 +2624,11 @@ public int getReferencedName(final MutableDirectBuffer dst, final int dstOffset, public int getReferencedName(final byte[] dst, final int dstOffset, final int length) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onReferencedNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1663,6 +2641,11 @@ public int getReferencedName(final byte[] dst, final int dstOffset, final int le public void wrapReferencedName(final DirectBuffer wrapBuffer) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onReferencedNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); @@ -1672,6 +2655,11 @@ public void wrapReferencedName(final DirectBuffer wrapBuffer) public String referencedName() { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onReferencedNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); final int dataLength = (buffer.getShort(limit, java.nio.ByteOrder.LITTLE_ENDIAN) & 0xFFFF); diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/TokenCodecEncoder.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/TokenCodecEncoder.java index 793f10756f..a0ece1099d 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/TokenCodecEncoder.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/generated/TokenCodecEncoder.java @@ -11,6 +11,130 @@ @SuppressWarnings("all") public final class TokenCodecEncoder { + private static final boolean ENABLE_BOUNDS_CHECKS = !Boolean.getBoolean("agrona.disable.bounds.checks"); + + private static final boolean SBE_ENABLE_IR_PRECEDENCE_CHECKS = Boolean.parseBoolean(System.getProperty( + "sbe.enable.ir.precedence.checks", + Boolean.toString(ENABLE_BOUNDS_CHECKS))); + + /** + * The states in which a encoder/decoder/codec can live. + * + *

The state machine diagram below, encoded in the dot language, describes + * the valid state transitions according to the order in which fields may be + * accessed safely. Tools such as PlantUML and Graphviz can render it. + * + *

{@code
+     *   digraph G {
+     *       NOT_WRAPPED -> V0_BLOCK [label="  wrap(version=0)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  tokenOffset(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  tokenSize(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  fieldId(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  tokenVersion(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  componentTokenCount(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  signal(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  primitiveType(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  byteOrder(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  presence(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  deprecated(?)  "];
+     *       V0_BLOCK -> V0_BLOCK [label="  nameLength()  "];
+     *       V0_BLOCK -> V0_NAME_DONE [label="  name(?)  "];
+     *       V0_NAME_DONE -> V0_NAME_DONE [label="  constValueLength()  "];
+     *       V0_NAME_DONE -> V0_CONSTVALUE_DONE [label="  constValue(?)  "];
+     *       V0_CONSTVALUE_DONE -> V0_CONSTVALUE_DONE [label="  minValueLength()  "];
+     *       V0_CONSTVALUE_DONE -> V0_MINVALUE_DONE [label="  minValue(?)  "];
+     *       V0_MINVALUE_DONE -> V0_MINVALUE_DONE [label="  maxValueLength()  "];
+     *       V0_MINVALUE_DONE -> V0_MAXVALUE_DONE [label="  maxValue(?)  "];
+     *       V0_MAXVALUE_DONE -> V0_MAXVALUE_DONE [label="  nullValueLength()  "];
+     *       V0_MAXVALUE_DONE -> V0_NULLVALUE_DONE [label="  nullValue(?)  "];
+     *       V0_NULLVALUE_DONE -> V0_NULLVALUE_DONE [label="  characterEncodingLength()  "];
+     *       V0_NULLVALUE_DONE -> V0_CHARACTERENCODING_DONE [label="  characterEncoding(?)  "];
+     *       V0_CHARACTERENCODING_DONE -> V0_CHARACTERENCODING_DONE [label="  epochLength()  "];
+     *       V0_CHARACTERENCODING_DONE -> V0_EPOCH_DONE [label="  epoch(?)  "];
+     *       V0_EPOCH_DONE -> V0_EPOCH_DONE [label="  timeUnitLength()  "];
+     *       V0_EPOCH_DONE -> V0_TIMEUNIT_DONE [label="  timeUnit(?)  "];
+     *       V0_TIMEUNIT_DONE -> V0_TIMEUNIT_DONE [label="  semanticTypeLength()  "];
+     *       V0_TIMEUNIT_DONE -> V0_SEMANTICTYPE_DONE [label="  semanticType(?)  "];
+     *       V0_SEMANTICTYPE_DONE -> V0_SEMANTICTYPE_DONE [label="  descriptionLength()  "];
+     *       V0_SEMANTICTYPE_DONE -> V0_DESCRIPTION_DONE [label="  description(?)  "];
+     *       V0_DESCRIPTION_DONE -> V0_DESCRIPTION_DONE [label="  referencedNameLength()  "];
+     *       V0_DESCRIPTION_DONE -> V0_REFERENCEDNAME_DONE [label="  referencedName(?)  "];
+     *   }
+     * }
+ */ + private static class CodecStates + { + private static final int NOT_WRAPPED = 0; + private static final int V0_BLOCK = 1; + private static final int V0_NAME_DONE = 2; + private static final int V0_CONSTVALUE_DONE = 3; + private static final int V0_MINVALUE_DONE = 4; + private static final int V0_MAXVALUE_DONE = 5; + private static final int V0_NULLVALUE_DONE = 6; + private static final int V0_CHARACTERENCODING_DONE = 7; + private static final int V0_EPOCH_DONE = 8; + private static final int V0_TIMEUNIT_DONE = 9; + private static final int V0_SEMANTICTYPE_DONE = 10; + private static final int V0_DESCRIPTION_DONE = 11; + private static final int V0_REFERENCEDNAME_DONE = 12; + + private static final String[] STATE_NAME_LOOKUP = + { + "NOT_WRAPPED", + "V0_BLOCK", + "V0_NAME_DONE", + "V0_CONSTVALUE_DONE", + "V0_MINVALUE_DONE", + "V0_MAXVALUE_DONE", + "V0_NULLVALUE_DONE", + "V0_CHARACTERENCODING_DONE", + "V0_EPOCH_DONE", + "V0_TIMEUNIT_DONE", + "V0_SEMANTICTYPE_DONE", + "V0_DESCRIPTION_DONE", + "V0_REFERENCEDNAME_DONE", + }; + + private static final String[] STATE_TRANSITIONS_LOOKUP = + { + "\"wrap(version=0)\"", + "\"tokenOffset(?)\", \"tokenSize(?)\", \"fieldId(?)\", \"tokenVersion(?)\", \"componentTokenCount(?)\", \"signal(?)\", \"primitiveType(?)\", \"byteOrder(?)\", \"presence(?)\", \"deprecated(?)\", \"nameLength()\", \"name(?)\"", + "\"constValueLength()\", \"constValue(?)\"", + "\"minValueLength()\", \"minValue(?)\"", + "\"maxValueLength()\", \"maxValue(?)\"", + "\"nullValueLength()\", \"nullValue(?)\"", + "\"characterEncodingLength()\", \"characterEncoding(?)\"", + "\"epochLength()\", \"epoch(?)\"", + "\"timeUnitLength()\", \"timeUnit(?)\"", + "\"semanticTypeLength()\", \"semanticType(?)\"", + "\"descriptionLength()\", \"description(?)\"", + "\"referencedNameLength()\", \"referencedName(?)\"", + "", + }; + + private static String name(final int state) + { + return STATE_NAME_LOOKUP[state]; + } + + private static String transitions(final int state) + { + return STATE_TRANSITIONS_LOOKUP[state]; + } + } + + private int codecState = CodecStates.NOT_WRAPPED; + + private int codecState() + { + return codecState; + } + + private void codecState(int newState) + { + codecState = newState; + } + public static final int BLOCK_LENGTH = 28; public static final int TEMPLATE_ID = 2; public static final int SCHEMA_ID = 1; @@ -74,6 +198,11 @@ public TokenCodecEncoder wrap(final MutableDirectBuffer buffer, final int offset this.offset = offset; limit(offset + BLOCK_LENGTH); + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + codecState(CodecStates.V0_BLOCK); + } + return this; } @@ -135,6 +264,17 @@ public static String tokenOffsetMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onTokenOffsetAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"tokenOffset\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public static int tokenOffsetNullValue() { return -2147483648; @@ -152,6 +292,11 @@ public static int tokenOffsetMaxValue() public TokenCodecEncoder tokenOffset(final int value) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTokenOffsetAccessed(); + } + buffer.putInt(offset + 0, value, java.nio.ByteOrder.LITTLE_ENDIAN); return this; } @@ -187,6 +332,17 @@ public static String tokenSizeMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onTokenSizeAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"tokenSize\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public static int tokenSizeNullValue() { return -2147483648; @@ -204,6 +360,11 @@ public static int tokenSizeMaxValue() public TokenCodecEncoder tokenSize(final int value) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTokenSizeAccessed(); + } + buffer.putInt(offset + 4, value, java.nio.ByteOrder.LITTLE_ENDIAN); return this; } @@ -239,6 +400,17 @@ public static String fieldIdMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onFieldIdAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"fieldId\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public static int fieldIdNullValue() { return -2147483648; @@ -256,6 +428,11 @@ public static int fieldIdMaxValue() public TokenCodecEncoder fieldId(final int value) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onFieldIdAccessed(); + } + buffer.putInt(offset + 8, value, java.nio.ByteOrder.LITTLE_ENDIAN); return this; } @@ -291,6 +468,17 @@ public static String tokenVersionMetaAttribute(final MetaAttribute metaAttribute return ""; } + private void onTokenVersionAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"tokenVersion\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public static int tokenVersionNullValue() { return -2147483648; @@ -308,6 +496,11 @@ public static int tokenVersionMaxValue() public TokenCodecEncoder tokenVersion(final int value) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTokenVersionAccessed(); + } + buffer.putInt(offset + 12, value, java.nio.ByteOrder.LITTLE_ENDIAN); return this; } @@ -343,6 +536,17 @@ public static String componentTokenCountMetaAttribute(final MetaAttribute metaAt return ""; } + private void onComponentTokenCountAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"componentTokenCount\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public static int componentTokenCountNullValue() { return -2147483648; @@ -360,6 +564,11 @@ public static int componentTokenCountMaxValue() public TokenCodecEncoder componentTokenCount(final int value) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onComponentTokenCountAccessed(); + } + buffer.putInt(offset + 16, value, java.nio.ByteOrder.LITTLE_ENDIAN); return this; } @@ -395,8 +604,24 @@ public static String signalMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onSignalAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"signal\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder signal(final SignalCodec value) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSignalAccessed(); + } + buffer.putByte(offset + 20, (byte)value.value()); return this; } @@ -431,8 +656,24 @@ public static String primitiveTypeMetaAttribute(final MetaAttribute metaAttribut return ""; } + private void onPrimitiveTypeAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"primitiveType\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder primitiveType(final PrimitiveTypeCodec value) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPrimitiveTypeAccessed(); + } + buffer.putByte(offset + 21, (byte)value.value()); return this; } @@ -467,8 +708,24 @@ public static String byteOrderMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onByteOrderAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"byteOrder\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder byteOrder(final ByteOrderCodec value) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onByteOrderAccessed(); + } + buffer.putByte(offset + 22, (byte)value.value()); return this; } @@ -503,8 +760,24 @@ public static String presenceMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onPresenceAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"presence\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder presence(final PresenceCodec value) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onPresenceAccessed(); + } + buffer.putByte(offset + 23, (byte)value.value()); return this; } @@ -539,6 +812,17 @@ public static String deprecatedMetaAttribute(final MetaAttribute metaAttribute) return ""; } + private void onDeprecatedAccessed() + { + if (codecState() == CodecStates.NOT_WRAPPED) + { + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"deprecated\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public static int deprecatedNullValue() { return 0; @@ -556,6 +840,11 @@ public static int deprecatedMaxValue() public TokenCodecEncoder deprecated(final int value) { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onDeprecatedAccessed(); + } + buffer.putInt(offset + 24, value, java.nio.ByteOrder.LITTLE_ENDIAN); return this; } @@ -586,6 +875,21 @@ public static int nameHeaderLength() return 2; } + private void onNameAccessed() + { + switch (codecState()) + { + case CodecStates.V0_BLOCK: + codecState(CodecStates.V0_NAME_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"name\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder putName(final DirectBuffer src, final int srcOffset, final int length) { if (length > 65534) @@ -593,6 +897,11 @@ public TokenCodecEncoder putName(final DirectBuffer src, final int srcOffset, fi throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -609,6 +918,11 @@ public TokenCodecEncoder putName(final byte[] src, final int srcOffset, final in throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -628,6 +942,11 @@ public TokenCodecEncoder name(final String value) throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -662,6 +981,21 @@ public static int constValueHeaderLength() return 2; } + private void onConstValueAccessed() + { + switch (codecState()) + { + case CodecStates.V0_NAME_DONE: + codecState(CodecStates.V0_CONSTVALUE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"constValue\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder putConstValue(final DirectBuffer src, final int srcOffset, final int length) { if (length > 65534) @@ -669,6 +1003,11 @@ public TokenCodecEncoder putConstValue(final DirectBuffer src, final int srcOffs throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onConstValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -685,6 +1024,11 @@ public TokenCodecEncoder putConstValue(final byte[] src, final int srcOffset, fi throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onConstValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -704,6 +1048,11 @@ public TokenCodecEncoder constValue(final String value) throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onConstValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -738,6 +1087,21 @@ public static int minValueHeaderLength() return 2; } + private void onMinValueAccessed() + { + switch (codecState()) + { + case CodecStates.V0_CONSTVALUE_DONE: + codecState(CodecStates.V0_MINVALUE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"minValue\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder putMinValue(final DirectBuffer src, final int srcOffset, final int length) { if (length > 65534) @@ -745,6 +1109,11 @@ public TokenCodecEncoder putMinValue(final DirectBuffer src, final int srcOffset throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMinValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -761,6 +1130,11 @@ public TokenCodecEncoder putMinValue(final byte[] src, final int srcOffset, fina throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMinValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -780,6 +1154,11 @@ public TokenCodecEncoder minValue(final String value) throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMinValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -814,6 +1193,21 @@ public static int maxValueHeaderLength() return 2; } + private void onMaxValueAccessed() + { + switch (codecState()) + { + case CodecStates.V0_MINVALUE_DONE: + codecState(CodecStates.V0_MAXVALUE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"maxValue\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder putMaxValue(final DirectBuffer src, final int srcOffset, final int length) { if (length > 65534) @@ -821,6 +1215,11 @@ public TokenCodecEncoder putMaxValue(final DirectBuffer src, final int srcOffset throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMaxValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -837,6 +1236,11 @@ public TokenCodecEncoder putMaxValue(final byte[] src, final int srcOffset, fina throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMaxValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -856,6 +1260,11 @@ public TokenCodecEncoder maxValue(final String value) throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onMaxValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -890,6 +1299,21 @@ public static int nullValueHeaderLength() return 2; } + private void onNullValueAccessed() + { + switch (codecState()) + { + case CodecStates.V0_MAXVALUE_DONE: + codecState(CodecStates.V0_NULLVALUE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"nullValue\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder putNullValue(final DirectBuffer src, final int srcOffset, final int length) { if (length > 65534) @@ -897,6 +1321,11 @@ public TokenCodecEncoder putNullValue(final DirectBuffer src, final int srcOffse throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNullValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -913,6 +1342,11 @@ public TokenCodecEncoder putNullValue(final byte[] src, final int srcOffset, fin throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNullValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -932,6 +1366,11 @@ public TokenCodecEncoder nullValue(final String value) throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onNullValueAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -966,6 +1405,21 @@ public static int characterEncodingHeaderLength() return 2; } + private void onCharacterEncodingAccessed() + { + switch (codecState()) + { + case CodecStates.V0_NULLVALUE_DONE: + codecState(CodecStates.V0_CHARACTERENCODING_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"characterEncoding\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder putCharacterEncoding(final DirectBuffer src, final int srcOffset, final int length) { if (length > 65534) @@ -973,6 +1427,11 @@ public TokenCodecEncoder putCharacterEncoding(final DirectBuffer src, final int throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onCharacterEncodingAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -989,6 +1448,11 @@ public TokenCodecEncoder putCharacterEncoding(final byte[] src, final int srcOff throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onCharacterEncodingAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1008,6 +1472,11 @@ public TokenCodecEncoder characterEncoding(final String value) throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onCharacterEncodingAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1042,6 +1511,21 @@ public static int epochHeaderLength() return 2; } + private void onEpochAccessed() + { + switch (codecState()) + { + case CodecStates.V0_CHARACTERENCODING_DONE: + codecState(CodecStates.V0_EPOCH_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"epoch\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder putEpoch(final DirectBuffer src, final int srcOffset, final int length) { if (length > 65534) @@ -1049,6 +1533,11 @@ public TokenCodecEncoder putEpoch(final DirectBuffer src, final int srcOffset, f throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onEpochAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1065,6 +1554,11 @@ public TokenCodecEncoder putEpoch(final byte[] src, final int srcOffset, final i throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onEpochAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1084,6 +1578,11 @@ public TokenCodecEncoder epoch(final String value) throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onEpochAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1118,6 +1617,21 @@ public static int timeUnitHeaderLength() return 2; } + private void onTimeUnitAccessed() + { + switch (codecState()) + { + case CodecStates.V0_EPOCH_DONE: + codecState(CodecStates.V0_TIMEUNIT_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"timeUnit\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder putTimeUnit(final DirectBuffer src, final int srcOffset, final int length) { if (length > 65534) @@ -1125,6 +1639,11 @@ public TokenCodecEncoder putTimeUnit(final DirectBuffer src, final int srcOffset throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTimeUnitAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1141,6 +1660,11 @@ public TokenCodecEncoder putTimeUnit(final byte[] src, final int srcOffset, fina throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTimeUnitAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1160,6 +1684,11 @@ public TokenCodecEncoder timeUnit(final String value) throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onTimeUnitAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1194,6 +1723,21 @@ public static int semanticTypeHeaderLength() return 2; } + private void onSemanticTypeAccessed() + { + switch (codecState()) + { + case CodecStates.V0_TIMEUNIT_DONE: + codecState(CodecStates.V0_SEMANTICTYPE_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"semanticType\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder putSemanticType(final DirectBuffer src, final int srcOffset, final int length) { if (length > 65534) @@ -1201,6 +1745,11 @@ public TokenCodecEncoder putSemanticType(final DirectBuffer src, final int srcOf throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticTypeAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1217,6 +1766,11 @@ public TokenCodecEncoder putSemanticType(final byte[] src, final int srcOffset, throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticTypeAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1236,6 +1790,11 @@ public TokenCodecEncoder semanticType(final String value) throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onSemanticTypeAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1270,6 +1829,21 @@ public static int descriptionHeaderLength() return 2; } + private void onDescriptionAccessed() + { + switch (codecState()) + { + case CodecStates.V0_SEMANTICTYPE_DONE: + codecState(CodecStates.V0_DESCRIPTION_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"description\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder putDescription(final DirectBuffer src, final int srcOffset, final int length) { if (length > 65534) @@ -1277,6 +1851,11 @@ public TokenCodecEncoder putDescription(final DirectBuffer src, final int srcOff throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onDescriptionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1293,6 +1872,11 @@ public TokenCodecEncoder putDescription(final byte[] src, final int srcOffset, f throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onDescriptionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1312,6 +1896,11 @@ public TokenCodecEncoder description(final String value) throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onDescriptionAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1346,6 +1935,21 @@ public static int referencedNameHeaderLength() return 2; } + private void onReferencedNameAccessed() + { + switch (codecState()) + { + case CodecStates.V0_DESCRIPTION_DONE: + codecState(CodecStates.V0_REFERENCEDNAME_DONE); + break; + default: + throw new IllegalStateException("Illegal field access order. " + + "Cannot access field \"referencedName\" in state: " + CodecStates.name(codecState()) + + ". Expected one of these transitions: [" + CodecStates.transitions(codecState()) + + "]. Please see the diagram in the Javadoc of the class TokenCodecEncoder#CodecStates."); + } + } + public TokenCodecEncoder putReferencedName(final DirectBuffer src, final int srcOffset, final int length) { if (length > 65534) @@ -1353,6 +1957,11 @@ public TokenCodecEncoder putReferencedName(final DirectBuffer src, final int src throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onReferencedNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1369,6 +1978,11 @@ public TokenCodecEncoder putReferencedName(final byte[] src, final int srcOffset throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onReferencedNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1388,6 +2002,11 @@ public TokenCodecEncoder referencedName(final String value) throw new IllegalStateException("length > maxValue for type: " + length); } + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + onReferencedNameAccessed(); + } + final int headerLength = 2; final int limit = parentMessage.limit(); parentMessage.limit(limit + headerLength + length); @@ -1419,4 +2038,21 @@ public StringBuilder appendTo(final StringBuilder builder) return decoder.appendTo(builder); } + + public void checkEncodingIsComplete() + { + if (SBE_ENABLE_IR_PRECEDENCE_CHECKS) + { + switch (codecState) + { + case CodecStates.V0_REFERENCEDNAME_DONE: + return; + default: + throw new IllegalStateException("Not fully encoded, current state: " + + CodecStates.name(codecState) + ", allowed transitions: " + + CodecStates.transitions(codecState)); + } + } + } + } diff --git a/sbe-tool/src/test/cpp/BoundsCheckTest.cpp b/sbe-tool/src/test/cpp/BoundsCheckTest.cpp index e3b81677dc..2adf48f06c 100644 --- a/sbe-tool/src/test/cpp/BoundsCheckTest.cpp +++ b/sbe-tool/src/test/cpp/BoundsCheckTest.cpp @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include #include #include @@ -217,43 +216,43 @@ class BoundsCheckTest : public testing::Test performanceFigures.next(); EXPECT_EQ(performanceFigures.octaneRating(), 95); - Car::PerformanceFigures::Acceleration &acceleration = performanceFigures.acceleration(); - EXPECT_EQ(acceleration.count(), 3u); - EXPECT_TRUE(acceleration.hasNext()); - acceleration.next(); - EXPECT_EQ(acceleration.mph(), 30); - EXPECT_EQ(acceleration.seconds(), 4.0f); + Car::PerformanceFigures::Acceleration &acceleration1 = performanceFigures.acceleration(); + EXPECT_EQ(acceleration1.count(), 3u); + EXPECT_TRUE(acceleration1.hasNext()); + acceleration1.next(); + EXPECT_EQ(acceleration1.mph(), 30); + EXPECT_EQ(acceleration1.seconds(), 4.0f); - EXPECT_TRUE(acceleration.hasNext()); - acceleration.next(); - EXPECT_EQ(acceleration.mph(), 60); - EXPECT_EQ(acceleration.seconds(), 7.5f); + EXPECT_TRUE(acceleration1.hasNext()); + acceleration1.next(); + EXPECT_EQ(acceleration1.mph(), 60); + EXPECT_EQ(acceleration1.seconds(), 7.5f); - EXPECT_TRUE(acceleration.hasNext()); - acceleration.next(); - EXPECT_EQ(acceleration.mph(), 100); - EXPECT_EQ(acceleration.seconds(), 12.2f); + EXPECT_TRUE(acceleration1.hasNext()); + acceleration1.next(); + EXPECT_EQ(acceleration1.mph(), 100); + EXPECT_EQ(acceleration1.seconds(), 12.2f); EXPECT_TRUE(performanceFigures.hasNext()); performanceFigures.next(); EXPECT_EQ(performanceFigures.octaneRating(), 99); - acceleration = performanceFigures.acceleration(); - EXPECT_EQ(acceleration.count(), 3u); - EXPECT_TRUE(acceleration.hasNext()); - acceleration.next(); - EXPECT_EQ(acceleration.mph(), 30); - EXPECT_EQ(acceleration.seconds(), 3.8f); - - EXPECT_TRUE(acceleration.hasNext()); - acceleration.next(); - EXPECT_EQ(acceleration.mph(), 60); - EXPECT_EQ(acceleration.seconds(), 7.1f); - - EXPECT_TRUE(acceleration.hasNext()); - acceleration.next(); - EXPECT_EQ(acceleration.mph(), 100); - EXPECT_EQ(acceleration.seconds(), 11.8f); + Car::PerformanceFigures::Acceleration &acceleration2 = performanceFigures.acceleration(); + EXPECT_EQ(acceleration2.count(), 3u); + EXPECT_TRUE(acceleration2.hasNext()); + acceleration2.next(); + EXPECT_EQ(acceleration2.mph(), 30); + EXPECT_EQ(acceleration2.seconds(), 3.8f); + + EXPECT_TRUE(acceleration2.hasNext()); + acceleration2.next(); + EXPECT_EQ(acceleration2.mph(), 60); + EXPECT_EQ(acceleration2.seconds(), 7.1f); + + EXPECT_TRUE(acceleration2.hasNext()); + acceleration2.next(); + EXPECT_EQ(acceleration2.mph(), 100); + EXPECT_EQ(acceleration2.seconds(), 11.8f); return m_carDecoder.encodedLength(); } diff --git a/sbe-tool/src/test/cpp/CMakeLists.txt b/sbe-tool/src/test/cpp/CMakeLists.txt index 03d1ab1cfb..8c698c83c3 100644 --- a/sbe-tool/src/test/cpp/CMakeLists.txt +++ b/sbe-tool/src/test/cpp/CMakeLists.txt @@ -18,6 +18,7 @@ function(sbe_test name) add_executable("${name}" "${name}.cpp") target_include_directories("${name}" PRIVATE ${GTEST_SOURCE_DIR}/googletest/include + PRIVATE ${GTEST_SOURCE_DIR}/googlemock/include PRIVATE ${CXX_CODEC_TARGET_DIR} ) target_compile_options("${name}" PRIVATE $<$:-Werror>) @@ -40,6 +41,7 @@ set(MESSAGE_BLOCK_LENGTH_TEST ${CODEC_SCHEMA_DIR}/message-block-length-test.xml) set(GROUP_WITH_DATA_SCHEMA ${CODEC_SCHEMA_DIR}/group-with-data-schema.xml) set(ISSUE835_SCHEMA ${CODEC_SCHEMA_DIR}/issue835.xml) set(ISSUE889_SCHEMA ${CODEC_SCHEMA_DIR}/issue889.xml) +set(ACCESS_ORDER_SCHEMA ${CODEC_SCHEMA_DIR}/field-order-check-schema.xml) set(GENERATED_CODECS ${CXX_CODEC_TARGET_DIR} @@ -54,12 +56,16 @@ add_custom_command( ${GROUP_WITH_DATA_SCHEMA} ${ISSUE835_SCHEMA} ${ISSUE889_SCHEMA} + ${ACCESS_ORDER_SCHEMA} sbe-jar ${SBE_JAR} COMMAND ${Java_JAVA_EXECUTABLE} -Dsbe.output.dir=${CXX_CODEC_TARGET_DIR} -Dsbe.generate.ir="true" -Dsbe.target.language="cpp" + -Dsbe.generate.precedence.checks="true" + -Dsbe.precedence.checks.flag.name="SBE_ENABLE_PRECEDENCE_CHECKS_IN_TESTS" + -Dsbe.cpp.disable.implicit.copying="true" -jar ${SBE_JAR} ${CODE_GENERATION_SCHEMA} ${COMPOSITE_OFFSETS_SCHEMA} @@ -68,6 +74,7 @@ add_custom_command( ${COMPOSITE_ELEMENTS_SCHEMA} ${ISSUE835_SCHEMA} ${ISSUE889_SCHEMA} + ${ACCESS_ORDER_SCHEMA} ) add_custom_target(codecs DEPENDS ${GENERATED_CODECS}) @@ -83,3 +90,5 @@ sbe_test(Rc3OtfFullIrTest codecs) sbe_test(CompositeElementsTest codecs) sbe_test(Issue835Test codecs) sbe_test(Issue889Test codecs) +sbe_test(FieldAccessOrderCheckTest codecs) +target_compile_definitions(FieldAccessOrderCheckTest PRIVATE SBE_ENABLE_PRECEDENCE_CHECKS_IN_TESTS) diff --git a/sbe-tool/src/test/cpp/CodeGenTest.cpp b/sbe-tool/src/test/cpp/CodeGenTest.cpp index 698d7ec849..6675172c8d 100644 --- a/sbe-tool/src/test/cpp/CodeGenTest.cpp +++ b/sbe-tool/src/test/cpp/CodeGenTest.cpp @@ -680,43 +680,43 @@ TEST_F(CodeGenTest, shouldBeAbleToEncodeAndDecodeHeaderPlusCarCorrectly) performanceFigures.next(); EXPECT_EQ(performanceFigures.octaneRating(), perf1Octane); - Car::PerformanceFigures::Acceleration &acceleration = performanceFigures.acceleration(); - EXPECT_EQ(acceleration.count(), ACCELERATION_COUNT); - ASSERT_TRUE(acceleration.hasNext()); - acceleration.next(); - EXPECT_EQ(acceleration.mph(), perf1aMph); - EXPECT_EQ(acceleration.seconds(), perf1aSeconds); - - ASSERT_TRUE(acceleration.hasNext()); - acceleration.next(); - EXPECT_EQ(acceleration.mph(), perf1bMph); - EXPECT_EQ(acceleration.seconds(), perf1bSeconds); - - ASSERT_TRUE(acceleration.hasNext()); - acceleration.next(); - EXPECT_EQ(acceleration.mph(), perf1cMph); - EXPECT_EQ(acceleration.seconds(), perf1cSeconds); + Car::PerformanceFigures::Acceleration &acceleration1 = performanceFigures.acceleration(); + EXPECT_EQ(acceleration1.count(), ACCELERATION_COUNT); + ASSERT_TRUE(acceleration1.hasNext()); + acceleration1.next(); + EXPECT_EQ(acceleration1.mph(), perf1aMph); + EXPECT_EQ(acceleration1.seconds(), perf1aSeconds); + + ASSERT_TRUE(acceleration1.hasNext()); + acceleration1.next(); + EXPECT_EQ(acceleration1.mph(), perf1bMph); + EXPECT_EQ(acceleration1.seconds(), perf1bSeconds); + + ASSERT_TRUE(acceleration1.hasNext()); + acceleration1.next(); + EXPECT_EQ(acceleration1.mph(), perf1cMph); + EXPECT_EQ(acceleration1.seconds(), perf1cSeconds); ASSERT_TRUE(performanceFigures.hasNext()); performanceFigures.next(); EXPECT_EQ(performanceFigures.octaneRating(), perf2Octane); - acceleration = performanceFigures.acceleration(); - EXPECT_EQ(acceleration.count(), ACCELERATION_COUNT); - ASSERT_TRUE(acceleration.hasNext()); - acceleration.next(); - EXPECT_EQ(acceleration.mph(), perf2aMph); - EXPECT_EQ(acceleration.seconds(), perf2aSeconds); + Car::PerformanceFigures::Acceleration &acceleration2 = performanceFigures.acceleration(); + EXPECT_EQ(acceleration2.count(), ACCELERATION_COUNT); + ASSERT_TRUE(acceleration2.hasNext()); + acceleration2.next(); + EXPECT_EQ(acceleration2.mph(), perf2aMph); + EXPECT_EQ(acceleration2.seconds(), perf2aSeconds); - ASSERT_TRUE(acceleration.hasNext()); - acceleration.next(); - EXPECT_EQ(acceleration.mph(), perf2bMph); - EXPECT_EQ(acceleration.seconds(), perf2bSeconds); + ASSERT_TRUE(acceleration2.hasNext()); + acceleration2.next(); + EXPECT_EQ(acceleration2.mph(), perf2bMph); + EXPECT_EQ(acceleration2.seconds(), perf2bSeconds); - ASSERT_TRUE(acceleration.hasNext()); - acceleration.next(); - EXPECT_EQ(acceleration.mph(), perf2cMph); - EXPECT_EQ(acceleration.seconds(), perf2cSeconds); + ASSERT_TRUE(acceleration2.hasNext()); + acceleration2.next(); + EXPECT_EQ(acceleration2.mph(), perf2cMph); + EXPECT_EQ(acceleration2.seconds(), perf2cSeconds); EXPECT_EQ(m_carDecoder.manufacturerLength(), MANUFACTURER_LENGTH); EXPECT_EQ(std::string(m_carDecoder.manufacturer(), MANUFACTURER_LENGTH), MANUFACTURER); @@ -748,7 +748,7 @@ struct CallbacksForEach void operator()(Car::PerformanceFigures &performanceFigures) { - Car::PerformanceFigures::Acceleration acceleration = performanceFigures.acceleration(); + Car::PerformanceFigures::Acceleration &acceleration = performanceFigures.acceleration(); countOfPerformanceFigures++; acceleration.forEach(*this); @@ -806,7 +806,7 @@ TEST_F(CodeGenTest, shouldBeAbleUseOnStackCodecsAndGroupForEach) #if __cplusplus >= 201103L performanceFigures.forEach([&](Car::PerformanceFigures &figures) { - Car::PerformanceFigures::Acceleration acceleration = figures.acceleration(); + Car::PerformanceFigures::Acceleration &acceleration = figures.acceleration(); cbs.countOfPerformanceFigures++; acceleration.forEach( @@ -964,13 +964,13 @@ TEST_F(CodeGenTest, shouldBeAbleToUseStdStringMethodsForDecode) Car::PerformanceFigures &perfFigures = carDecoder.performanceFigures(); perfFigures.next(); - Car::PerformanceFigures::Acceleration &acceleration = perfFigures.acceleration(); + Car::PerformanceFigures::Acceleration &acceleration1 = perfFigures.acceleration(); - acceleration.next().next().next(); + acceleration1.next().next().next(); perfFigures.next(); - acceleration = perfFigures.acceleration(); - acceleration.next().next().next(); + Car::PerformanceFigures::Acceleration &acceleration2 = perfFigures.acceleration(); + acceleration2.next().next().next(); EXPECT_EQ(carDecoder.getManufacturerAsString(), manufacturer); EXPECT_EQ(carDecoder.getModelAsString(), model); @@ -1037,15 +1037,15 @@ TEST_F(CodeGenTest, shouldPrintFullDecodedFlyweightRegardlessOfReadPosition) Car::PerformanceFigures &perfFigures = carDecoder.performanceFigures(); perfFigures.next(); - Car::PerformanceFigures::Acceleration &acceleration = perfFigures.acceleration(); + Car::PerformanceFigures::Acceleration &acceleration1 = perfFigures.acceleration(); - acceleration.next().next().next(); + acceleration1.next().next().next(); expectDisplayString(expectedDisplayString, carDecoder); perfFigures.next(); - acceleration = perfFigures.acceleration(); - acceleration.next().next().next(); + Car::PerformanceFigures::Acceleration &acceleration2 = perfFigures.acceleration(); + acceleration2.next().next().next(); EXPECT_EQ(carDecoder.getManufacturerAsString(), manufacturer); EXPECT_EQ(carDecoder.getModelAsString(), model); diff --git a/sbe-tool/src/test/cpp/CompositeOffsetsCodeGenTest.cpp b/sbe-tool/src/test/cpp/CompositeOffsetsCodeGenTest.cpp index 28bcbb1880..39759c1d50 100644 --- a/sbe-tool/src/test/cpp/CompositeOffsetsCodeGenTest.cpp +++ b/sbe-tool/src/test/cpp/CompositeOffsetsCodeGenTest.cpp @@ -141,7 +141,7 @@ TEST_F(CompositeOffsetsCodeGenTest, shouldBeAbleToDecodeHeaderAndMsgCorrectly) m_msgDecoder.wrapForDecode( buffer, hdrSz, TestMessage1::sbeBlockLength(), TestMessage1::sbeSchemaVersion(), hdrSz + sz); - TestMessage1::Entries entries = m_msgDecoder.entries(); + TestMessage1::Entries &entries = m_msgDecoder.entries(); EXPECT_EQ(entries.count(), 2u); ASSERT_TRUE(entries.hasNext()); diff --git a/sbe-tool/src/test/cpp/FieldAccessOrderCheckTest.cpp b/sbe-tool/src/test/cpp/FieldAccessOrderCheckTest.cpp new file mode 100644 index 0000000000..89aeeca267 --- /dev/null +++ b/sbe-tool/src/test/cpp/FieldAccessOrderCheckTest.cpp @@ -0,0 +1,4937 @@ +/* + * Copyright 2013-2023 Real Logic Limited. + * + * 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 + * + * https://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. + */ + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "order_check/MultipleVarLength.h" +#include "order_check/GroupAndVarLength.h" +#include "order_check/VarLengthInsideGroup.h" +#include "order_check/NestedGroups.h" +#include "order_check/CompositeInsideGroup.h" +#include "order_check/AddPrimitiveV1.h" +#include "order_check/AddPrimitiveV0.h" +#include "order_check/AddPrimitiveBeforeGroupV1.h" +#include "order_check/AddPrimitiveBeforeGroupV0.h" +#include "order_check/AddPrimitiveBeforeVarDataV1.h" +#include "order_check/AddPrimitiveBeforeVarDataV0.h" +#include "order_check/AddPrimitiveInsideGroupV1.h" +#include "order_check/AddPrimitiveInsideGroupV0.h" +#include "order_check/AddGroupBeforeVarDataV1.h" +#include "order_check/AddGroupBeforeVarDataV0.h" +#include "order_check/AddEnumBeforeGroupV1.h" +#include "order_check/AddEnumBeforeGroupV0.h" +#include "order_check/AddCompositeBeforeGroupV1.h" +#include "order_check/AddCompositeBeforeGroupV0.h" +#include "order_check/AddArrayBeforeGroupV1.h" +#include "order_check/AddArrayBeforeGroupV0.h" +#include "order_check/AddBitSetBeforeGroupV1.h" +#include "order_check/AddBitSetBeforeGroupV0.h" +#include "order_check/EnumInsideGroup.h" +#include "order_check/BitSetInsideGroup.h" +#include "order_check/ArrayInsideGroup.h" +#include "order_check/MultipleGroups.h" +#include "order_check/AddVarDataV1.h" +#include "order_check/AddVarDataV0.h" +#include "order_check/AsciiInsideGroup.h" +#include "order_check/AddAsciiBeforeGroupV1.h" +#include "order_check/AddAsciiBeforeGroupV0.h" +#include "order_check/NoBlock.h" +#include "order_check/GroupWithNoBlock.h" +#include "order_check/NestedGroupWithVarLength.h" + +using namespace order::check; +using ::testing::HasSubstr; + +class FieldAccessOrderCheckTest : public testing::Test +{ +public: + static const std::size_t BUFFER_LEN = 2048u; + static const std::size_t OFFSET = 0; + + char m_buffer[BUFFER_LEN] = {}; + +}; + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingVariableLengthFieldsInSchemaDefinedOrder1) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.putB("abc"); + encoder.putC("def"); + encoder.checkEncodingIsComplete(); + + MultipleVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleVarLength::sbeBlockLength(), + MultipleVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + EXPECT_EQ(decoder.a(), 42); + EXPECT_EQ(decoder.getBAsString(), "abc"); + EXPECT_EQ(decoder.getCAsString(), "def"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingVariableLengthFieldsInSchemaDefinedOrder2) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.putB("abc"); + encoder.putC("def"); + encoder.checkEncodingIsComplete(); + + MultipleVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleVarLength::sbeBlockLength(), + MultipleVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + EXPECT_EQ(decoder.a(), 42); + auto bLength = decoder.bLength(); + auto *b = decoder.b(); + const std::string bStr(b, bLength); + EXPECT_EQ(bStr, "abc"); + auto cLength = decoder.cLength(); + auto *c = decoder.c(); + const std::string cStr(c, cLength); + EXPECT_EQ(cStr, "def"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingVariableLengthFieldsInSchemaDefinedOrder3) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + auto *bIn = "abc"; + encoder.putB(bIn, strlen(bIn)); + auto *cIn = "def"; + encoder.putC(cIn, strlen(cIn)); + encoder.checkEncodingIsComplete(); + + MultipleVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleVarLength::sbeBlockLength(), + MultipleVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + EXPECT_EQ(decoder.a(), 42); + + const auto bLength = decoder.bLength(); + auto *bOut = new char[bLength]; + decoder.getB(bOut, bLength); + EXPECT_EQ(std::strncmp(bOut, "abc", bLength), 0); + delete[] bOut; + + auto cLength = decoder.cLength(); + auto *cOut = new char[cLength]; + decoder.getC(cOut, cLength); + EXPECT_EQ(std::strncmp(cOut, "def", cLength), 0); + delete[] cOut; +} + +TEST_F(FieldAccessOrderCheckTest, allowsDecodingVariableLengthFieldsAfterRewind) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.putB("abc"); + encoder.putC("def"); + encoder.checkEncodingIsComplete(); + + MultipleVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleVarLength::sbeBlockLength(), + MultipleVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + EXPECT_EQ(decoder.a(), 42); + EXPECT_EQ(decoder.getBAsString(), "abc"); + EXPECT_EQ(decoder.getCAsString(), "def"); + + decoder.sbeRewind(); + + EXPECT_EQ(decoder.a(), 42); + EXPECT_EQ(decoder.getBAsString(), "abc"); + EXPECT_EQ(decoder.getCAsString(), "def"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsDecodingToSkipVariableLengthFields) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.putB("abc"); + encoder.putC("def"); + encoder.checkEncodingIsComplete(); + + MultipleVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleVarLength::sbeBlockLength(), + MultipleVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + EXPECT_EQ(decoder.a(), 42); + EXPECT_EQ(decoder.skipB(), 3ul); + EXPECT_EQ(decoder.getCAsString(), "def"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsReEncodingTopLevelPrimitiveFields) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.putB("abc"); + encoder.putC("def"); + encoder.a(43); + encoder.checkEncodingIsComplete(); + + MultipleVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleVarLength::sbeBlockLength(), + MultipleVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + EXPECT_EQ(decoder.a(), 43); + EXPECT_EQ(decoder.getBAsString(), "abc"); + EXPECT_EQ(decoder.getCAsString(), "def"); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsSkippingEncodingOfVariableLengthField1) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + EXPECT_THROW( + { + try + { + encoder.putC("def"); + } + catch (const std::exception &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"c\" in state: V0_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsSkippingEncodingOfVariableLengthField2) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + EXPECT_THROW( + { + try + { + auto *cIn = "cIn"; + encoder.putC(cIn, std::strlen(cIn)); + } + catch (const std::exception &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"c\" in state: V0_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsReEncodingEarlierVariableLengthFields) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.putB("abc"); + encoder.putC("def"); + EXPECT_THROW( + { + try + { + encoder.putB("ghi"); + } + catch (const std::exception &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b\" in state: V0_C_DONE") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsReEncodingLatestVariableLengthField) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.putB("abc"); + encoder.putC("def"); + EXPECT_THROW( + { + try + { + encoder.putC("ghi"); + } + catch (const std::exception &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"c\" in state: V0_C_DONE") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsSkippingDecodingOfVariableLengthField1) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.putB("abc"); + encoder.putC("def"); + encoder.checkEncodingIsComplete(); + + MultipleVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleVarLength::sbeBlockLength(), + MultipleVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + EXPECT_EQ(decoder.a(), 42); + + EXPECT_THROW( + { + try + { + decoder.getCAsString(); + } + catch (const std::exception &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"c\" in state: V0_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsSkippingDecodingOfVariableLengthField2) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.putB("abc"); + encoder.putC("def"); + encoder.checkEncodingIsComplete(); + + MultipleVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleVarLength::sbeBlockLength(), + MultipleVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + EXPECT_EQ(decoder.a(), 42); + + EXPECT_THROW( + { + try + { + decoder.cLength(); + } + catch (const std::exception &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot decode length of var data \"c\" in state: V0_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsSkippingDecodingOfVariableLengthField3) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.putB("abc"); + encoder.putC("def"); + encoder.checkEncodingIsComplete(); + + MultipleVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleVarLength::sbeBlockLength(), + MultipleVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + EXPECT_EQ(decoder.a(), 42); + + EXPECT_THROW( + { + try + { + char cOut[3]; + decoder.getC(cOut, 3); + } + catch (const std::exception &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"c\" in state: V0_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsReDecodingEarlierVariableLengthField) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.putB("abc"); + encoder.putC("def"); + encoder.checkEncodingIsComplete(); + + MultipleVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleVarLength::sbeBlockLength(), + MultipleVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + EXPECT_EQ(decoder.a(), 42); + EXPECT_EQ(decoder.getBAsString(), "abc"); + EXPECT_EQ(decoder.getCAsString(), "def"); + + EXPECT_THROW( + { + try + { + decoder.getBAsString(); + } + catch (const std::exception &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b\" in state: V0_C_DONE") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsReDecodingLatestVariableLengthField) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.putB("abc"); + encoder.putC("def"); + encoder.checkEncodingIsComplete(); + + MultipleVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleVarLength::sbeBlockLength(), + MultipleVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + EXPECT_EQ(decoder.a(), 42); + EXPECT_EQ(decoder.getBAsString(), "abc"); + EXPECT_EQ(decoder.getCAsString(), "def"); + + EXPECT_THROW( + { + try + { + decoder.getCAsString(); + } + catch (const std::exception &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"c\" in state: V0_C_DONE") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingGroupAndVariableLengthFieldsInSchemaDefinedOrder) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + GroupAndVarLength::B &b = encoder.bCount(2); + b.next().c(1); + b.next().c(2); + encoder.putD("abc"); + encoder.checkEncodingIsComplete(); + + GroupAndVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + GroupAndVarLength::sbeBlockLength(), + GroupAndVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + EXPECT_EQ(decoder.a(), 42); + GroupAndVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 1); + EXPECT_EQ(bDecoder.next().c(), 2); + EXPECT_EQ(decoder.getDAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsDecodingGroupAndVariableLengthFieldsAfterRewind) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + GroupAndVarLength::B &b = encoder.bCount(2); + b.next().c(1); + b.next().c(2); + encoder.putD("abc"); + encoder.checkEncodingIsComplete(); + + GroupAndVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + GroupAndVarLength::sbeBlockLength(), + GroupAndVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + EXPECT_EQ(decoder.a(), 42); + GroupAndVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 1); + EXPECT_EQ(bDecoder.next().c(), 2); + EXPECT_EQ(decoder.getDAsString(), "abc"); + + decoder.sbeRewind(); + + EXPECT_EQ(decoder.a(), 42); + GroupAndVarLength::B &bDecoder2 = decoder.b(); + EXPECT_EQ(bDecoder2.count(), 2u); + EXPECT_EQ(bDecoder2.next().c(), 1); + EXPECT_EQ(bDecoder2.next().c(), 2); + EXPECT_EQ(decoder.getDAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsDecodingToSkipMessage) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + GroupAndVarLength::B &b = encoder.bCount(2); + b.next().c(1); + b.next().c(2); + encoder.putD("abc"); + encoder.checkEncodingIsComplete(); + + const std::uint64_t nextEncodeOffset = encoder.sbePosition(); + encoder.wrapForEncode(m_buffer, nextEncodeOffset, BUFFER_LEN); + encoder.a(43); + GroupAndVarLength::B &b2 = encoder.bCount(2); + b2.next().c(3); + b2.next().c(4); + encoder.putD("def"); + encoder.checkEncodingIsComplete(); + + GroupAndVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + GroupAndVarLength::sbeBlockLength(), + GroupAndVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + decoder.skip(); + const std::uint64_t nextDecodeOffset = decoder.sbePosition(); + EXPECT_EQ(nextDecodeOffset, nextEncodeOffset); + + decoder.wrapForDecode( + m_buffer, + nextDecodeOffset, + GroupAndVarLength::sbeBlockLength(), + GroupAndVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 43); + GroupAndVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 3); + EXPECT_EQ(bDecoder.next().c(), 4); + EXPECT_EQ(decoder.getDAsString(), "def"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsDecodingToDetermineMessageLengthBeforeReadingFields) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(43); + GroupAndVarLength::B &b = encoder.bCount(2); + b.next().c(3); + b.next().c(4); + encoder.putD("def"); + encoder.checkEncodingIsComplete(); + + GroupAndVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + GroupAndVarLength::sbeBlockLength(), + GroupAndVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + + EXPECT_EQ(decoder.decodeLength(), 18u); + EXPECT_EQ(decoder.a(), 43); + GroupAndVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 3); + EXPECT_EQ(bDecoder.next().c(), 4); + EXPECT_EQ(decoder.getDAsString(), "def"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingEmptyGroupAndVariableLengthFieldsInSchemaDefinedOrder) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.bCount(0); + encoder.putD("abc"); + encoder.checkEncodingIsComplete(); + + GroupAndVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + GroupAndVarLength::sbeBlockLength(), + GroupAndVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + GroupAndVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 0u); + EXPECT_EQ(decoder.getDAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncoderToResetZeroGroupLengthToNonZero) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.bCount(0).resetCountToIndex(); + encoder.putD("abc"); + encoder.checkEncodingIsComplete(); + + GroupAndVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + GroupAndVarLength::sbeBlockLength(), + GroupAndVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + GroupAndVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 0u); + EXPECT_EQ(decoder.getDAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncoderToResetNonZeroGroupLengthToNonZeroBeforeCallingNext) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.bCount(2).resetCountToIndex(); + encoder.putD("abc"); + encoder.checkEncodingIsComplete(); + + GroupAndVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + GroupAndVarLength::sbeBlockLength(), + GroupAndVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + GroupAndVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 0u); + EXPECT_EQ(decoder.getDAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncoderToResetNonZeroGroupLengthToNonZero) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + GroupAndVarLength::B &bEncoder = encoder.bCount(2).next(); + bEncoder.c(43); + bEncoder.resetCountToIndex(); + encoder.putD("abc"); + encoder.checkEncodingIsComplete(); + + GroupAndVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + GroupAndVarLength::sbeBlockLength(), + GroupAndVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + GroupAndVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + EXPECT_EQ(bDecoder.next().c(), 43); + EXPECT_EQ(decoder.getDAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsEncoderToResetGroupLengthMidGroupElement) +{ + NestedGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + NestedGroups::B &bEncoder = encoder.bCount(2).next(); + bEncoder.c(43); + EXPECT_THROW( + { + try + { + bEncoder.resetCountToIndex(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot reset count of repeating group \"b\" in state: V0_B_N_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + + +TEST_F(FieldAccessOrderCheckTest, disallowsEncodingGroupElementBeforeCallingNext) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + GroupAndVarLength::B &bEncoder = encoder.bCount(1); + EXPECT_THROW( + { + try + { + bEncoder.c(1); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsDecodingGroupElementBeforeCallingNext) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + GroupAndVarLength::B &bEncoder = encoder.bCount(2); + bEncoder.next().c(1); + bEncoder.next().c(2); + encoder.putD("abc"); + encoder.checkEncodingIsComplete(); + + GroupAndVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + GroupAndVarLength::sbeBlockLength(), + GroupAndVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + GroupAndVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + + EXPECT_THROW( + { + try + { + bDecoder.c(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsSkippingEncodingOfGroup) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + EXPECT_THROW( + { + try + { + encoder.putD("abc"); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"d\" in state: V0_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsReEncodingVariableLengthFieldAfterGroup) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + GroupAndVarLength::B &bEncoder = encoder.bCount(2); + bEncoder.next().c(1); + bEncoder.next().c(2); + encoder.putD("abc"); + EXPECT_THROW( + { + try + { + encoder.putD("def"); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"d\" in state: V0_D_DONE") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsReEncodingGroupCount) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + GroupAndVarLength::B &bEncoder = encoder.bCount(2); + bEncoder.next().c(1); + bEncoder.next().c(2); + encoder.putD("abc"); + EXPECT_THROW( + { + try + { + encoder.bCount(1); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot encode count of repeating group \"b\" in state: V0_D_DONE") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedDecodingOfGroupBeforeVariableLengthField) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + GroupAndVarLength::B &bEncoder = encoder.bCount(2); + bEncoder.next().c(1); + bEncoder.next().c(2); + encoder.putD("abc"); + encoder.checkEncodingIsComplete(); + + GroupAndVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + GroupAndVarLength::sbeBlockLength(), + GroupAndVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + EXPECT_THROW( + { + try + { + decoder.getDAsString(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"d\" in state: V0_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsReDecodingVariableLengthFieldAfterGroup) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + GroupAndVarLength::B &bEncoder = encoder.bCount(2); + bEncoder.next().c(1); + bEncoder.next().c(2); + encoder.putD("abc"); + encoder.checkEncodingIsComplete(); + + GroupAndVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + GroupAndVarLength::sbeBlockLength(), + GroupAndVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + GroupAndVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 1); + EXPECT_EQ(bDecoder.next().c(), 2); + EXPECT_EQ(decoder.getDAsString(), "abc"); + EXPECT_THROW( + { + try + { + decoder.getDAsString(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"d\" in state: V0_D_DONE") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsReDecodingGroupAfterVariableLengthField) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + GroupAndVarLength::B &bEncoder = encoder.bCount(2); + bEncoder.next().c(1); + bEncoder.next().c(2); + encoder.putD("abc"); + encoder.checkEncodingIsComplete(); + + GroupAndVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + GroupAndVarLength::sbeBlockLength(), + GroupAndVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + GroupAndVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 1); + EXPECT_EQ(bDecoder.next().c(), 2); + EXPECT_EQ(decoder.getDAsString(), "abc"); + EXPECT_THROW( + { + try + { + decoder.b(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot decode count of repeating group \"b\" in state: V0_D_DONE") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingVariableLengthFieldInsideGroupInSchemaDefinedOrder) +{ + VarLengthInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + VarLengthInsideGroup::B &bEncoder = encoder.bCount(2); + bEncoder.next().c(1).putD("abc"); + bEncoder.next().c(2).putD("def"); + encoder.putE("ghi"); + encoder.checkEncodingIsComplete(); + + VarLengthInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + VarLengthInsideGroup::sbeBlockLength(), + VarLengthInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + VarLengthInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 1); + EXPECT_EQ(bDecoder.getDAsString(), "abc"); + EXPECT_EQ(bDecoder.next().c(), 2); + EXPECT_EQ(bDecoder.getDAsString(), "def"); + EXPECT_EQ(decoder.getEAsString(), "ghi"); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedGroupElementVariableLengthFieldToEncodeAtTopLevel) +{ + VarLengthInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + VarLengthInsideGroup::B &bEncoder = encoder.bCount(1); + bEncoder.next().c(1); + EXPECT_THROW( + { + try + { + encoder.putE("abc"); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"e\" in state: V0_B_1_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedGroupElementVariableLengthFieldToEncodeNextElement) +{ + VarLengthInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + VarLengthInsideGroup::B &bEncoder = encoder.bCount(2); + bEncoder.next(); + EXPECT_THROW( + { + try + { + bEncoder.next(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access next element in repeating group \"b\" in state: V0_B_N_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedGroupElementEncoding) +{ + VarLengthInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + VarLengthInsideGroup::B &bEncoder = encoder.bCount(2); + bEncoder.next().c(1).putD("abc"); + EXPECT_THROW( + { + try + { + encoder.putE("abc"); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"e\" in state: V0_B_N_D_DONE") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsReEncodingGroupElementVariableLengthField) +{ + VarLengthInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + VarLengthInsideGroup::B &bEncoder = encoder.bCount(1); + bEncoder.next().c(1).putD("abc"); + encoder.putE("def"); + EXPECT_THROW( + { + try + { + bEncoder.putD("ghi"); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.d\" in state: V0_E_DONE") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsReDecodingGroupElementVariableLengthField) +{ + VarLengthInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + VarLengthInsideGroup::B &bEncoder = encoder.bCount(2); + bEncoder.next().c(1).putD("abc"); + bEncoder.next().c(2).putD("def"); + encoder.putE("ghi"); + encoder.checkEncodingIsComplete(); + + VarLengthInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + VarLengthInsideGroup::sbeBlockLength(), + VarLengthInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + VarLengthInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 1); + EXPECT_EQ(bDecoder.getDAsString(), "abc"); + EXPECT_THROW( + { + try + { + bDecoder.getDAsString(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.d\" in state: V0_B_N_D_DONE") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedDecodingOfGroupElementVariableLengthFieldToNextElement) +{ + VarLengthInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + VarLengthInsideGroup::B &bEncoder = encoder.bCount(2); + bEncoder.next().c(1).putD("abc"); + bEncoder.next().c(2).putD("def"); + encoder.putE("ghi"); + encoder.checkEncodingIsComplete(); + + VarLengthInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + VarLengthInsideGroup::sbeBlockLength(), + VarLengthInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + VarLengthInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 1); + EXPECT_THROW( + { + try + { + bDecoder.next(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access next element in repeating group \"b\" in state: V0_B_N_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedDecodingOfGroupElementVariableLengthFieldToTopLevel) +{ + VarLengthInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + VarLengthInsideGroup::B &bEncoder = encoder.bCount(1); + bEncoder.next().c(1).putD("abc"); + encoder.putE("ghi"); + encoder.checkEncodingIsComplete(); + + VarLengthInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + VarLengthInsideGroup::sbeBlockLength(), + VarLengthInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + VarLengthInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + EXPECT_EQ(bDecoder.next().c(), 1); + EXPECT_THROW( + { + try + { + decoder.getEAsString(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"e\" in state: V0_B_1_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedDecodingOfGroupElement) +{ + VarLengthInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + VarLengthInsideGroup::B &bEncoder = encoder.bCount(2); + bEncoder.next().c(1).putD("abc"); + bEncoder.next().c(2).putD("def"); + encoder.putE("ghi"); + encoder.checkEncodingIsComplete(); + + VarLengthInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + VarLengthInsideGroup::sbeBlockLength(), + VarLengthInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + VarLengthInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 1); + EXPECT_THROW( + { + try + { + decoder.getEAsString(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"e\" in state: V0_B_N_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingNestedGroupsInSchemaDefinedOrder) +{ + NestedGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + NestedGroups::B &bEncoder = encoder.bCount(2); + bEncoder.next(); + bEncoder.c(1); + NestedGroups::B::D &b0dEncoder = bEncoder.dCount(2); + b0dEncoder.next().e(2); + b0dEncoder.next().e(3); + NestedGroups::B::F &b0fEncoder = bEncoder.fCount(1); + b0fEncoder.next().g(4); + bEncoder.next(); + bEncoder.c(5); + NestedGroups::B::D &b1dEncoder = bEncoder.dCount(1); + b1dEncoder.next().e(6); + NestedGroups::B::F &b1fEncoder = bEncoder.fCount(1); + b1fEncoder.next().g(7); + NestedGroups::H &hEncoder = encoder.hCount(1); + hEncoder.next().i(8); + encoder.checkEncodingIsComplete(); + + NestedGroups decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + NestedGroups::sbeBlockLength(), + NestedGroups::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + NestedGroups::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + NestedGroups::B &b0Decoder = bDecoder.next(); + EXPECT_EQ(b0Decoder.c(), 1); + NestedGroups::B::D &b0dDecoder = b0Decoder.d(); + EXPECT_EQ(b0dDecoder.count(), 2u); + EXPECT_EQ(b0dDecoder.next().e(), 2); + EXPECT_EQ(b0dDecoder.next().e(), 3); + NestedGroups::B::F &b0fDecoder = b0Decoder.f(); + EXPECT_EQ(b0fDecoder.count(), 1u); + EXPECT_EQ(b0fDecoder.next().g(), 4); + NestedGroups::B &b1Decoder = bDecoder.next(); + EXPECT_EQ(b1Decoder.c(), 5); + NestedGroups::B::D &b1dDecoder = b1Decoder.d(); + EXPECT_EQ(b1dDecoder.count(), 1u); + EXPECT_EQ(b1dDecoder.next().e(), 6); + NestedGroups::B::F &b1fDecoder = b1Decoder.f(); + EXPECT_EQ(b1fDecoder.count(), 1u); + EXPECT_EQ(b1fDecoder.next().g(), 7); + NestedGroups::H &hDecoder = decoder.h(); + EXPECT_EQ(hDecoder.count(), 1u); + EXPECT_EQ(hDecoder.next().i(), 8); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingEmptyNestedGroupsInSchemaDefinedOrder) +{ + NestedGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.bCount(0); + encoder.hCount(0); + encoder.checkEncodingIsComplete(); + + NestedGroups decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + NestedGroups::sbeBlockLength(), + NestedGroups::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + NestedGroups::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 0u); + NestedGroups::H &hDecoder = decoder.h(); + EXPECT_EQ(hDecoder.count(), 0u); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedEncodingOfNestedGroup) +{ + NestedGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + NestedGroups::B &bEncoder = encoder.bCount(1).next(); + bEncoder.c(1); + EXPECT_THROW( + { + try + { + bEncoder.fCount(1); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot encode count of repeating group \"b.f\" in state: V0_B_1_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingCompositeInsideGroupInSchemaDefinedOrder) +{ + CompositeInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Point &aEncoder = encoder.a(); + aEncoder.x(1); + aEncoder.y(2); + CompositeInsideGroup::B &bEncoder = encoder.bCount(1).next(); + Point &cEncoder = bEncoder.c(); + cEncoder.x(3); + cEncoder.y(4); + encoder.checkEncodingIsComplete(); + + CompositeInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + CompositeInsideGroup::sbeBlockLength(), + CompositeInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + Point &aDecoder = decoder.a(); + EXPECT_EQ(aDecoder.x(), 1); + EXPECT_EQ(aDecoder.y(), 2); + CompositeInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + Point &cDecoder = bDecoder.next().c(); + EXPECT_EQ(cDecoder.x(), 3); + EXPECT_EQ(cDecoder.y(), 4); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsEncodingCompositeInsideGroupBeforeCallingNext) +{ + CompositeInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Point &aEncoder = encoder.a(); + aEncoder.x(1); + aEncoder.y(2); + CompositeInsideGroup::B &bEncoder = encoder.bCount(1); + EXPECT_THROW( + { + try + { + bEncoder.c(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsDecodingCompositeInsideGroupBeforeCallingNext) +{ + CompositeInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Point &aEncoder = encoder.a(); + aEncoder.x(1); + aEncoder.y(2); + CompositeInsideGroup::B &bEncoder = encoder.bCount(1).next(); + Point &cEncoder = bEncoder.c(); + cEncoder.x(3); + cEncoder.y(4); + + CompositeInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + CompositeInsideGroup::sbeBlockLength(), + CompositeInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + Point &aDecoder = decoder.a(); + EXPECT_EQ(aDecoder.x(), 1); + EXPECT_EQ(aDecoder.y(), 2); + CompositeInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + EXPECT_THROW( + { + try + { + bDecoder.c(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, allowsReEncodingTopLevelCompositeViaReWrap) +{ + CompositeInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Point &aEncoder = encoder.a(); + aEncoder.x(1); + aEncoder.y(2); + CompositeInsideGroup::B &bEncoder = encoder.bCount(1).next(); + Point &cEncoder = bEncoder.c(); + cEncoder.x(3); + cEncoder.y(4); + encoder.a().x(5); + encoder.a().y(6); + encoder.checkEncodingIsComplete(); + + CompositeInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + CompositeInsideGroup::sbeBlockLength(), + CompositeInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + Point &aDecoder = decoder.a(); + EXPECT_EQ(aDecoder.x(), 5); + EXPECT_EQ(aDecoder.y(), 6); + CompositeInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + Point &cDecoder = bDecoder.next().c(); + EXPECT_EQ(cDecoder.x(), 3); + EXPECT_EQ(cDecoder.y(), 4); +} + +TEST_F(FieldAccessOrderCheckTest, allowsReEncodingTopLevelCompositeViaEncoderReference) +{ + CompositeInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Point &aEncoder = encoder.a(); + aEncoder.x(1); + aEncoder.y(2); + CompositeInsideGroup::B &bEncoder = encoder.bCount(1).next(); + Point &cEncoder = bEncoder.c(); + cEncoder.x(3); + cEncoder.y(4); + aEncoder.x(5); + aEncoder.y(6); + encoder.checkEncodingIsComplete(); + + CompositeInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + CompositeInsideGroup::sbeBlockLength(), + CompositeInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + Point &aDecoder = decoder.a(); + EXPECT_EQ(aDecoder.x(), 5); + EXPECT_EQ(aDecoder.y(), 6); + CompositeInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + Point &cDecoder = bDecoder.next().c(); + EXPECT_EQ(cDecoder.x(), 3); + EXPECT_EQ(cDecoder.y(), 4); +} + +TEST_F(FieldAccessOrderCheckTest, allowsReEncodingGroupElementCompositeViaReWrap) +{ + CompositeInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Point &aEncoder = encoder.a(); + aEncoder.x(1); + aEncoder.y(2); + CompositeInsideGroup::B &bEncoder = encoder.bCount(1).next(); + bEncoder.c().x(3).y(4); + bEncoder.c().x(5).y(6); + encoder.checkEncodingIsComplete(); + + CompositeInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + CompositeInsideGroup::sbeBlockLength(), + CompositeInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + Point &aDecoder = decoder.a(); + EXPECT_EQ(aDecoder.x(), 1); + EXPECT_EQ(aDecoder.y(), 2); + CompositeInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + Point &cDecoder = bDecoder.next().c(); + EXPECT_EQ(cDecoder.x(), 5); + EXPECT_EQ(cDecoder.y(), 6); +} + +TEST_F(FieldAccessOrderCheckTest, allowsReEncodingGroupElementCompositeViaEncoderReference) +{ + CompositeInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Point &aEncoder = encoder.a(); + aEncoder.x(1); + aEncoder.y(2); + CompositeInsideGroup::B &bEncoder = encoder.bCount(1).next(); + Point &cEncoder = bEncoder.c(); + cEncoder.x(3); + cEncoder.y(4); + cEncoder.x(5); + cEncoder.y(6); + encoder.checkEncodingIsComplete(); + + CompositeInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + CompositeInsideGroup::sbeBlockLength(), + CompositeInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + Point &aDecoder = decoder.a(); + EXPECT_EQ(aDecoder.x(), 1); + EXPECT_EQ(aDecoder.y(), 2); + CompositeInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + Point &cDecoder = bDecoder.next().c(); + EXPECT_EQ(cDecoder.x(), 5); + EXPECT_EQ(cDecoder.y(), 6); +} + +TEST_F(FieldAccessOrderCheckTest, allowsReDecodingTopLevelCompositeViaReWrap) +{ + CompositeInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Point &aEncoder = encoder.a(); + aEncoder.x(1); + aEncoder.y(2); + CompositeInsideGroup::B &bEncoder = encoder.bCount(1).next(); + bEncoder.c().x(3).y(4); + encoder.checkEncodingIsComplete(); + + CompositeInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + CompositeInsideGroup::sbeBlockLength(), + CompositeInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + Point &a1Decoder = decoder.a(); + EXPECT_EQ(a1Decoder.x(), 1); + EXPECT_EQ(a1Decoder.y(), 2); + CompositeInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + Point &cDecoder = bDecoder.next().c(); + EXPECT_EQ(cDecoder.x(), 3); + EXPECT_EQ(cDecoder.y(), 4); + Point &a2Decoder = decoder.a(); + EXPECT_EQ(a2Decoder.x(), 1); + EXPECT_EQ(a2Decoder.y(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsReDecodingTopLevelCompositeViaEncoderReference) +{ + CompositeInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Point &aEncoder = encoder.a(); + aEncoder.x(1); + aEncoder.y(2); + CompositeInsideGroup::B &bEncoder = encoder.bCount(1).next(); + bEncoder.c().x(3).y(4); + encoder.checkEncodingIsComplete(); + + CompositeInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + CompositeInsideGroup::sbeBlockLength(), + CompositeInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + Point &aDecoder = decoder.a(); + EXPECT_EQ(aDecoder.x(), 1); + EXPECT_EQ(aDecoder.y(), 2); + CompositeInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + Point &cDecoder = bDecoder.next().c(); + EXPECT_EQ(cDecoder.x(), 3); + EXPECT_EQ(cDecoder.y(), 4); + EXPECT_EQ(aDecoder.x(), 1); + EXPECT_EQ(aDecoder.y(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsReDecodingGroupElementCompositeViaReWrap) +{ + CompositeInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Point &aEncoder = encoder.a(); + aEncoder.x(1); + aEncoder.y(2); + CompositeInsideGroup::B &bEncoder = encoder.bCount(1).next(); + bEncoder.c().x(3).y(4); + encoder.checkEncodingIsComplete(); + + CompositeInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + CompositeInsideGroup::sbeBlockLength(), + CompositeInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + Point &aDecoder = decoder.a(); + EXPECT_EQ(aDecoder.x(), 1); + EXPECT_EQ(aDecoder.y(), 2); + CompositeInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + Point &c1Decoder = bDecoder.next().c(); + EXPECT_EQ(c1Decoder.x(), 3); + EXPECT_EQ(c1Decoder.y(), 4); + Point &c2Decoder = bDecoder.c(); + EXPECT_EQ(c2Decoder.x(), 3); + EXPECT_EQ(c2Decoder.y(), 4); +} + +TEST_F(FieldAccessOrderCheckTest, allowsReDecodingGroupElementCompositeViaEncoderReference) +{ + CompositeInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Point &aEncoder = encoder.a(); + aEncoder.x(1); + aEncoder.y(2); + CompositeInsideGroup::B &bEncoder = encoder.bCount(1).next(); + bEncoder.c().x(3).y(4); + encoder.checkEncodingIsComplete(); + + CompositeInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + CompositeInsideGroup::sbeBlockLength(), + CompositeInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + Point &aDecoder = decoder.a(); + EXPECT_EQ(aDecoder.x(), 1); + EXPECT_EQ(aDecoder.y(), 2); + CompositeInsideGroup::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + Point &cDecoder = bDecoder.next().c(); + EXPECT_EQ(cDecoder.x(), 3); + EXPECT_EQ(cDecoder.y(), 4); + EXPECT_EQ(cDecoder.x(), 3); + EXPECT_EQ(cDecoder.y(), 4); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedPrimitiveField) +{ + AddPrimitiveV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.b(2); + encoder.checkEncodingIsComplete(); + + AddPrimitiveV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddPrimitiveV1::sbeBlockLength(), + AddPrimitiveV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.b(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingPrimitiveFieldAsNullValue) +{ + AddPrimitiveV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.checkEncodingIsComplete(); + + AddPrimitiveV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddPrimitiveV0::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.b(), AddPrimitiveV1::bNullValue()); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedPrimitiveFieldBeforeGroup) +{ + AddPrimitiveBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.d(3); + AddPrimitiveBeforeGroupV1::B &bEncoder = encoder.bCount(1).next(); + bEncoder.c(2); + encoder.checkEncodingIsComplete(); + + AddPrimitiveBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddPrimitiveBeforeGroupV1::sbeBlockLength(), + AddPrimitiveBeforeGroupV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.d(), 3); + AddPrimitiveBeforeGroupV1::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + EXPECT_EQ(bDecoder.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingPrimitiveFieldBeforeGroupAsNullValue) +{ + AddPrimitiveBeforeGroupV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + AddPrimitiveBeforeGroupV0::B &bEncoder = encoder.bCount(1).next(); + bEncoder.c(2); + encoder.checkEncodingIsComplete(); + + AddPrimitiveBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddPrimitiveBeforeGroupV0::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.d(), AddPrimitiveBeforeGroupV1::dNullValue()); + AddPrimitiveBeforeGroupV1::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + EXPECT_EQ(bDecoder.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToSkipPresentButAddedPrimitiveFieldBeforeGroup) +{ + AddPrimitiveBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.d(3); + AddPrimitiveBeforeGroupV1::B &bEncoder = encoder.bCount(1).next(); + bEncoder.c(2); + encoder.checkEncodingIsComplete(); + + AddPrimitiveBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddPrimitiveBeforeGroupV1::sbeBlockLength(), + AddPrimitiveBeforeGroupV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddPrimitiveBeforeGroupV1::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + EXPECT_EQ(bDecoder.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsOldDecoderToSkipAddedPrimitiveFieldBeforeGroup) +{ + AddPrimitiveBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.d(3); + AddPrimitiveBeforeGroupV1::B &bEncoder = encoder.bCount(1).next(); + bEncoder.c(2); + encoder.checkEncodingIsComplete(); + + AddPrimitiveBeforeGroupV0 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddPrimitiveBeforeGroupV1::sbeBlockLength(), + 1, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddPrimitiveBeforeGroupV0::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + EXPECT_EQ(bDecoder.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedPrimitiveFieldBeforeVarData) +{ + AddPrimitiveBeforeVarDataV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.c(3); + encoder.putB("abc"); + encoder.checkEncodingIsComplete(); + + AddPrimitiveBeforeVarDataV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddPrimitiveBeforeVarDataV1::sbeBlockLength(), + AddPrimitiveBeforeVarDataV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.c(), 3); + EXPECT_EQ(decoder.getBAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingPrimitiveFieldBeforeVarDataAsNullValue) +{ + AddPrimitiveBeforeVarDataV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.putB("abc"); + encoder.checkEncodingIsComplete(); + + AddPrimitiveBeforeVarDataV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddPrimitiveBeforeVarDataV0::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.c(), AddPrimitiveBeforeVarDataV1::cNullValue()); + EXPECT_EQ(decoder.getBAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToSkipPresentButAddedPrimitiveFieldBeforeVarData) +{ + AddPrimitiveBeforeVarDataV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.c(3); + encoder.putB("abc"); + encoder.checkEncodingIsComplete(); + + AddPrimitiveBeforeVarDataV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddPrimitiveBeforeVarDataV1::sbeBlockLength(), + AddPrimitiveBeforeVarDataV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.getBAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsOldDecoderToSkipAddedPrimitiveFieldBeforeVarData) +{ + AddPrimitiveBeforeVarDataV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.c(3); + encoder.putB("abc"); + encoder.checkEncodingIsComplete(); + + AddPrimitiveBeforeVarDataV0 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddPrimitiveBeforeVarDataV1::sbeBlockLength(), + 1, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.getBAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedPrimitiveFieldInsideGroup) +{ + AddPrimitiveInsideGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.bCount(1).next().c(2).d(3); + encoder.checkEncodingIsComplete(); + + AddPrimitiveInsideGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddPrimitiveInsideGroupV1::sbeBlockLength(), + AddPrimitiveInsideGroupV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddPrimitiveInsideGroupV1::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + EXPECT_EQ(bDecoder.next().c(), 2); + EXPECT_EQ(bDecoder.d(), 3); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingPrimitiveFieldInsideGroupAsNullValue) +{ + AddPrimitiveInsideGroupV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddPrimitiveInsideGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddPrimitiveInsideGroupV0::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddPrimitiveInsideGroupV1::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + EXPECT_EQ(bDecoder.next().c(), 2); + EXPECT_EQ(bDecoder.d(), AddPrimitiveInsideGroupV1::B::dNullValue()); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToSkipPresentButAddedPrimitiveFieldInsideGroup) +{ + AddPrimitiveInsideGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.bCount(2).next().c(2).d(3).next().c(4).d(5); + encoder.checkEncodingIsComplete(); + + AddPrimitiveInsideGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddPrimitiveInsideGroupV1::sbeBlockLength(), + AddPrimitiveInsideGroupV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddPrimitiveInsideGroupV1::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 2); + EXPECT_EQ(bDecoder.next().c(), 4); +} + +TEST_F(FieldAccessOrderCheckTest, allowsOldDecoderToSkipAddedPrimitiveFieldInsideGroup) +{ + AddPrimitiveInsideGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.bCount(2).next().c(2).d(3).next().c(4).d(5); + encoder.checkEncodingIsComplete(); + + AddPrimitiveInsideGroupV0 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddPrimitiveInsideGroupV1::sbeBlockLength(), + 1, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddPrimitiveInsideGroupV0::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 2); + EXPECT_EQ(bDecoder.next().c(), 4); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedGroupBeforeVarData) +{ + AddGroupBeforeVarDataV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.cCount(1).next().d(2); + encoder.putB("abc"); + encoder.checkEncodingIsComplete(); + + AddGroupBeforeVarDataV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddGroupBeforeVarDataV1::sbeBlockLength(), + AddGroupBeforeVarDataV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddGroupBeforeVarDataV1::C &cDecoder = decoder.c(); + EXPECT_EQ(cDecoder.count(), 1u); + EXPECT_EQ(cDecoder.next().d(), 2); + EXPECT_EQ(decoder.getBAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingGroupBeforeVarDataAsNullValue) +{ + AddGroupBeforeVarDataV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.putB("abc"); + encoder.checkEncodingIsComplete(); + + AddGroupBeforeVarDataV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddGroupBeforeVarDataV0::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddGroupBeforeVarDataV1::C &cDecoder = decoder.c(); + EXPECT_EQ(cDecoder.count(), 0u); + EXPECT_EQ(decoder.getBAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToSkipMissingGroupBeforeVarData) +{ + AddGroupBeforeVarDataV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.putB("abc"); + encoder.checkEncodingIsComplete(); + + AddGroupBeforeVarDataV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddGroupBeforeVarDataV0::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.getBAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsNewDecoderToSkipPresentButAddedGroupBeforeVarData) +{ + AddGroupBeforeVarDataV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.cCount(1).next().d(2); + encoder.putB("abc"); + encoder.checkEncodingIsComplete(); + + AddGroupBeforeVarDataV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddGroupBeforeVarDataV1::sbeBlockLength(), + AddGroupBeforeVarDataV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_THROW( + { + try + { + decoder.b(); + } + catch (const std::exception &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b\" in state: V1_BLOCK") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, allowsOldDecoderToSkipAddedGroupBeforeVarData) +{ + AddGroupBeforeVarDataV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.cCount(1).next().d(2); + encoder.putB("abc"); + encoder.checkEncodingIsComplete(); + + AddGroupBeforeVarDataV0 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddGroupBeforeVarDataV1::sbeBlockLength(), + AddGroupBeforeVarDataV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + + // In reality, we would use a numGroups field in the message header to skip over unknown groups + // rather than the hardcoded knowledge of one extra group below: + + GroupSizeEncoding groupSizeEncodingDecoder; + groupSizeEncodingDecoder.wrap( + m_buffer, + decoder.sbePosition(), + GroupSizeEncoding::sbeSchemaVersion(), + BUFFER_LEN + ); + const std::uint64_t bytesToSkip = GroupSizeEncoding::encodedLength() + + groupSizeEncodingDecoder.blockLength() * groupSizeEncodingDecoder.numInGroup(); + decoder.sbePosition(decoder.sbePosition() + bytesToSkip); + + EXPECT_EQ(decoder.getBAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedEnumFieldBeforeGroup) +{ + AddEnumBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.d(Direction::BUY); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddEnumBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddEnumBeforeGroupV1::sbeBlockLength(), + 1, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.d(), Direction::BUY); + AddEnumBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingEnumFieldBeforeGroupAsNullValue) +{ + AddEnumBeforeGroupV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddEnumBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddEnumBeforeGroupV0::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.d(), Direction::NULL_VALUE); + AddEnumBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToSkipPresentButAddedEnumFieldBeforeGroup) +{ + AddEnumBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.d(Direction::SELL); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddEnumBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddEnumBeforeGroupV1::sbeBlockLength(), + 1, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddEnumBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsOldDecoderToSkipAddedEnumFieldBeforeGroup) +{ + AddEnumBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.d(Direction::BUY); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddEnumBeforeGroupV0 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddEnumBeforeGroupV1::sbeBlockLength(), + 1, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddEnumBeforeGroupV0::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedCompositeFieldBeforeGroup) +{ + AddCompositeBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + Point &d = encoder.d(); + d.x(-1); + d.y(-2); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddCompositeBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddCompositeBeforeGroupV1::sbeBlockLength(), + 1, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + Point &d2 = decoder.d(); + EXPECT_EQ(d2.x(), -1); + EXPECT_EQ(d2.y(), -2); + AddCompositeBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToSkipPresentButAddedCompositeFieldBeforeGroup) +{ + AddCompositeBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + Point &d = encoder.d(); + d.x(-1); + d.y(-2); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddCompositeBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddCompositeBeforeGroupV1::sbeBlockLength(), + 1, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddCompositeBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsOldDecoderToSkipAddedCompositeFieldBeforeGroup) +{ + AddCompositeBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + Point &d = encoder.d(); + d.x(-1); + d.y(-2); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddCompositeBeforeGroupV0 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddCompositeBeforeGroupV1::sbeBlockLength(), + 1, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddCompositeBeforeGroupV0::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedArrayFieldBeforeGroup) +{ + AddArrayBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.putD(1, 2, 3, 4); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddArrayBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddArrayBeforeGroupV1::sbeBlockLength(), + 1, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.d(0), 1); + EXPECT_EQ(decoder.d(1), 2); + EXPECT_EQ(decoder.d(2), 3); + EXPECT_EQ(decoder.d(3), 4); + AddArrayBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingArrayFieldBeforeGroupAsNullValue1) +{ + AddArrayBeforeGroupV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddArrayBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddArrayBeforeGroupV0::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.d(0), AddArrayBeforeGroupV1::dNullValue()); + EXPECT_EQ(decoder.d(1), AddArrayBeforeGroupV1::dNullValue()); + EXPECT_EQ(decoder.d(2), AddArrayBeforeGroupV1::dNullValue()); + EXPECT_EQ(decoder.d(3), AddArrayBeforeGroupV1::dNullValue()); + AddArrayBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingArrayFieldBeforeGroupAsNullValue2) +{ + AddArrayBeforeGroupV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddArrayBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddArrayBeforeGroupV0::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + char *d = decoder.d(); + EXPECT_EQ(d, nullptr); + AddArrayBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingArrayFieldBeforeGroupAsNullValue3) +{ + AddArrayBeforeGroupV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddArrayBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddArrayBeforeGroupV0::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + const AddArrayBeforeGroupV1 &constDecoder = decoder; + const char *d = constDecoder.d(); + EXPECT_EQ(d, nullptr); + AddArrayBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToSkipPresentButAddedArrayFieldBeforeGroup) +{ + AddArrayBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.putD(1, 2, 3, 4); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddArrayBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddArrayBeforeGroupV1::sbeBlockLength(), + 1, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddArrayBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsOldDecoderToSkipAddedArrayFieldBeforeGroup) +{ + AddArrayBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.putD(1, 2, 3, 4); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddArrayBeforeGroupV0 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddArrayBeforeGroupV1::sbeBlockLength(), + 1, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddArrayBeforeGroupV0::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedBitSetFieldBeforeGroup) +{ + AddBitSetBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1).d().guacamole(true).cheese(true).sourCream(false); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddBitSetBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddBitSetBeforeGroupV1::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + Flags &d = decoder.d(); + EXPECT_EQ(d.guacamole(), true); + EXPECT_EQ(d.cheese(), true); + EXPECT_EQ(d.sourCream(), false); + AddBitSetBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingBitSetFieldBeforeGroupAsNullValue) +{ + AddBitSetBeforeGroupV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddBitSetBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddBitSetBeforeGroupV0::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.dInActingVersion(), false); + AddBitSetBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToSkipPresentButAddedBitSetFieldBeforeGroup) +{ + AddBitSetBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1).d().guacamole(true).cheese(true).sourCream(false); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddBitSetBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddBitSetBeforeGroupV1::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddBitSetBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsOldDecoderToSkipAddedBitSetFieldBeforeGroup) +{ + AddBitSetBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1).d().guacamole(true).cheese(true).sourCream(false); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + AddBitSetBeforeGroupV0 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddBitSetBeforeGroupV1::sbeBlockLength(), + 1, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddBitSetBeforeGroupV0::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingEnumInsideGroupInSchemaDefinedOrder) +{ + EnumInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(Direction::BUY) + .bCount(1) + .next() + .c(Direction::SELL); + encoder.checkEncodingIsComplete(); + + EnumInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + EnumInsideGroup::sbeBlockLength(), + EnumInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), Direction::BUY); + EnumInsideGroup::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), Direction::SELL); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsEncodingEnumInsideGroupBeforeCallingNext) +{ + EnumInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(Direction::BUY); + EnumInsideGroup::B &bEncoder = encoder.bCount(1); + EXPECT_THROW( + { + try + { + bEncoder.c(Direction::SELL); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsDecodingEnumInsideGroupBeforeCallingNext) +{ + EnumInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(Direction::BUY) + .bCount(1) + .next() + .c(Direction::SELL); + encoder.checkEncodingIsComplete(); + + EnumInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + EnumInsideGroup::sbeBlockLength(), + EnumInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), Direction::BUY); + EnumInsideGroup::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_THROW( + { + try + { + b.c(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, allowsReEncodingTopLevelEnum) +{ + EnumInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(Direction::BUY) + .bCount(1) + .next() + .c(Direction::SELL); + + encoder.a(Direction::SELL); + encoder.checkEncodingIsComplete(); + + EnumInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + EnumInsideGroup::sbeBlockLength(), + EnumInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), Direction::SELL); + EnumInsideGroup::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), Direction::SELL); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingBitSetInsideGroupInSchemaDefinedOrder) +{ + BitSetInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Flags &a = encoder.a(); + a.cheese(true).guacamole(true).sourCream(false); + BitSetInsideGroup::B &b = encoder.bCount(1) + .next(); + Flags &c = b.c(); + c.cheese(false).guacamole(false).sourCream(true); + encoder.checkEncodingIsComplete(); + + BitSetInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + BitSetInsideGroup::sbeBlockLength(), + BitSetInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + Flags &aFlags = decoder.a(); + EXPECT_EQ(aFlags.guacamole(), true); + EXPECT_EQ(aFlags.cheese(), true); + EXPECT_EQ(aFlags.sourCream(), false); + BitSetInsideGroup::B &bGroup = decoder.b(); + EXPECT_EQ(bGroup.count(), 1u); + Flags &cFlags = bGroup.next().c(); + EXPECT_EQ(cFlags.guacamole(), false); + EXPECT_EQ(cFlags.cheese(), false); + EXPECT_EQ(cFlags.sourCream(), true); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsEncodingBitSetInsideGroupBeforeCallingNext) +{ + BitSetInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Flags &a = encoder.a(); + a.cheese(true).guacamole(true).sourCream(false); + BitSetInsideGroup::B &b = encoder.bCount(1); + EXPECT_THROW( + { + try + { + b.c(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsDecodingBitSetInsideGroupBeforeCallingNext) +{ + BitSetInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Flags &a = encoder.a(); + a.cheese(true).guacamole(true).sourCream(false); + BitSetInsideGroup::B &b = encoder.bCount(1) + .next(); + Flags &c = b.c(); + c.cheese(false).guacamole(false).sourCream(true); + encoder.checkEncodingIsComplete(); + + BitSetInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + BitSetInsideGroup::sbeBlockLength(), + BitSetInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + Flags &aFlags = decoder.a(); + EXPECT_EQ(aFlags.guacamole(), true); + EXPECT_EQ(aFlags.cheese(), true); + EXPECT_EQ(aFlags.sourCream(), false); + BitSetInsideGroup::B &bGroup = decoder.b(); + EXPECT_EQ(bGroup.count(), 1u); + EXPECT_THROW( + { + try + { + bGroup.c(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, allowsReEncodingTopLevelBitSetViaReWrap) +{ + BitSetInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + Flags &a = encoder.a(); + a.cheese(true).guacamole(true).sourCream(false); + BitSetInsideGroup::B &b = encoder.bCount(1) + .next(); + Flags &c = b.c(); + c.cheese(false).guacamole(false).sourCream(true); + + Flags &aPrime = encoder.a(); + aPrime.sourCream(true); + encoder.checkEncodingIsComplete(); + + BitSetInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + BitSetInsideGroup::sbeBlockLength(), + BitSetInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + Flags &aFlags = decoder.a(); + EXPECT_EQ(aFlags.guacamole(), true); + EXPECT_EQ(aFlags.cheese(), true); + EXPECT_EQ(aFlags.sourCream(), true); + BitSetInsideGroup::B &bGroup = decoder.b(); + EXPECT_EQ(bGroup.count(), 1u); + Flags &cFlags = bGroup.next().c(); + EXPECT_EQ(cFlags.guacamole(), false); + EXPECT_EQ(cFlags.cheese(), false); + EXPECT_EQ(cFlags.sourCream(), true); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingArrayInsideGroupInSchemaDefinedOrder) +{ + ArrayInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA(1, 2, 3, 4); + ArrayInsideGroup::B &b = encoder.bCount(1) + .next(); + b.putC(5, 6, 7, 8); + encoder.checkEncodingIsComplete(); + + ArrayInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + ArrayInsideGroup::sbeBlockLength(), + ArrayInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(0), 1); + EXPECT_EQ(decoder.a(1), 2); + EXPECT_EQ(decoder.a(2), 3); + EXPECT_EQ(decoder.a(3), 4); + ArrayInsideGroup::B &bGroup = decoder.b(); + EXPECT_EQ(bGroup.count(), 1u); + bGroup.next(); + EXPECT_EQ(bGroup.c(0), 5); + EXPECT_EQ(bGroup.c(1), 6); + EXPECT_EQ(bGroup.c(2), 7); + EXPECT_EQ(bGroup.c(3), 8); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsEncodingArrayInsideGroupBeforeCallingNext1) +{ + ArrayInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA(1, 2, 3, 4); + ArrayInsideGroup::B &bEncoder = encoder.bCount(1); + EXPECT_THROW( + { + try + { + bEncoder.putC(5, 6, 7, 8); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr("Cannot access field \"b.c\" in state: V0_B_N")); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsEncodingArrayInsideGroupBeforeCallingNext2) +{ + ArrayInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA(1, 2, 3, 4); + ArrayInsideGroup::B &bEncoder = encoder.bCount(1); + const char c[4] = {5, 6, 7, 8}; + const char *cPtr = c; + EXPECT_THROW( + { + try + { + bEncoder.putC(cPtr); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr("Cannot access field \"b.c\" in state: V0_B_N")); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsDecodingArrayInsideGroupBeforeCallingNext1) +{ + ArrayInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA(1, 2, 3, 4); + ArrayInsideGroup::B &bEncoder = encoder.bCount(1); + bEncoder.next(); + bEncoder.putC(5, 6, 7, 8); + encoder.checkEncodingIsComplete(); + + ArrayInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + ArrayInsideGroup::sbeBlockLength(), + ArrayInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(0), 1); + EXPECT_EQ(decoder.a(1), 2); + EXPECT_EQ(decoder.a(2), 3); + EXPECT_EQ(decoder.a(3), 4); + ArrayInsideGroup::B &bGroup = decoder.b(); + EXPECT_EQ(bGroup.count(), 1u); + EXPECT_THROW( + { + try + { + bGroup.c(0); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr("Cannot access field \"b.c\" in state: V0_B_N")); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsDecodingArrayInsideGroupBeforeCallingNext2) +{ + ArrayInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA(1, 2, 3, 4); + ArrayInsideGroup::B &bEncoder = encoder.bCount(1); + bEncoder.next(); + bEncoder.putC(5, 6, 7, 8); + encoder.checkEncodingIsComplete(); + + ArrayInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + ArrayInsideGroup::sbeBlockLength(), + ArrayInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(0), 1); + EXPECT_EQ(decoder.a(1), 2); + EXPECT_EQ(decoder.a(2), 3); + EXPECT_EQ(decoder.a(3), 4); + ArrayInsideGroup::B &bGroup = decoder.b(); + EXPECT_EQ(bGroup.count(), 1u); + EXPECT_THROW( + { + try + { + const std::uint64_t charCount = 4; + char c[charCount]; + bGroup.getC(c, charCount); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr("Cannot access field \"b.c\" in state: V0_B_N")); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsDecodingArrayInsideGroupBeforeCallingNext3) +{ + ArrayInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA(1, 2, 3, 4); + ArrayInsideGroup::B &bEncoder = encoder.bCount(1); + bEncoder.next(); + bEncoder.putC(5, 6, 7, 8); + encoder.checkEncodingIsComplete(); + + ArrayInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + ArrayInsideGroup::sbeBlockLength(), + ArrayInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(0), 1); + EXPECT_EQ(decoder.a(1), 2); + EXPECT_EQ(decoder.a(2), 3); + EXPECT_EQ(decoder.a(3), 4); + ArrayInsideGroup::B &bGroup = decoder.b(); + EXPECT_EQ(bGroup.count(), 1u); + EXPECT_THROW( + { + try + { + bGroup.c(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr("Cannot access field \"b.c\" in state: V0_B_N")); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, allowsReEncodingTopLevelArray) +{ + ArrayInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA(1, 2, 3, 4); + ArrayInsideGroup::B &bEncoder = encoder.bCount(1); + bEncoder.next(); + bEncoder.putC(5, 6, 7, 8); + + encoder.putA(9, 10, 11, 12); + encoder.checkEncodingIsComplete(); + + ArrayInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + ArrayInsideGroup::sbeBlockLength(), + ArrayInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(0), 9); + EXPECT_EQ(decoder.a(1), 10); + EXPECT_EQ(decoder.a(2), 11); + EXPECT_EQ(decoder.a(3), 12); + ArrayInsideGroup::B &bGroup = decoder.b(); + EXPECT_EQ(bGroup.count(), 1u); + bGroup.next(); + EXPECT_EQ(bGroup.c(0), 5); + EXPECT_EQ(bGroup.c(1), 6); + EXPECT_EQ(bGroup.c(2), 7); + EXPECT_EQ(bGroup.c(3), 8); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingGroupFieldsInSchemaDefinedOrder1) +{ + MultipleGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.bCount(0); + MultipleGroups::D &dEncoder = encoder.dCount(1); + dEncoder.next(); + dEncoder.e(43); + encoder.checkEncodingIsComplete(); + + MultipleGroups decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleGroups::sbeBlockLength(), + MultipleGroups::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + MultipleGroups::B &bGroup = decoder.b(); + EXPECT_EQ(bGroup.count(), 0u); + MultipleGroups::D &dGroup = decoder.d(); + EXPECT_EQ(dGroup.count(), 1u); + dGroup.next(); + EXPECT_EQ(dGroup.e(), 43); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingGroupFieldsInSchemaDefinedOrder2) +{ + MultipleGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(41); + MultipleGroups::B &bEncoder = encoder.bCount(1); + bEncoder.next(); + bEncoder.c(42); + MultipleGroups::D &dEncoder = encoder.dCount(1); + dEncoder.next(); + dEncoder.e(43); + encoder.checkEncodingIsComplete(); + + MultipleGroups decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleGroups::sbeBlockLength(), + MultipleGroups::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 41); + MultipleGroups::B &bGroup = decoder.b(); + EXPECT_EQ(bGroup.count(), 1u); + bGroup.next(); + EXPECT_EQ(bGroup.c(), 42); + MultipleGroups::D &dGroup = decoder.d(); + EXPECT_EQ(dGroup.count(), 1u); + dGroup.next(); + EXPECT_EQ(dGroup.e(), 43); +} + +TEST_F(FieldAccessOrderCheckTest, allowsReEncodingTopLevelPrimitiveFieldsAfterGroups) +{ + MultipleGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(41); + MultipleGroups::B &bEncoder = encoder.bCount(1); + bEncoder.next(); + bEncoder.c(42); + MultipleGroups::D &dEncoder = encoder.dCount(1); + dEncoder.next(); + dEncoder.e(43); + encoder.a(44); + encoder.checkEncodingIsComplete(); + + MultipleGroups decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleGroups::sbeBlockLength(), + MultipleGroups::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 44); + MultipleGroups::B &bGroup = decoder.b(); + EXPECT_EQ(bGroup.count(), 1u); + bGroup.next(); + EXPECT_EQ(bGroup.c(), 42); + MultipleGroups::D &dGroup = decoder.d(); + EXPECT_EQ(dGroup.count(), 1u); + dGroup.next(); + EXPECT_EQ(dGroup.e(), 43); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedEncodingOfGroupField) +{ + MultipleGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(41); + EXPECT_THROW( + { + try + { + encoder.dCount(0); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot encode count of repeating group \"d\" in state: V0_BLOCK") + ); + throw; + } + }, std::logic_error); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsReEncodingEarlierGroupFields) +{ + MultipleGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(41); + MultipleGroups::B &bEncoder = encoder.bCount(1); + bEncoder.next(); + bEncoder.c(42); + MultipleGroups::D &dEncoder = encoder.dCount(1); + dEncoder.next(); + dEncoder.e(43); + EXPECT_THROW( + { + try + { + encoder.bCount(1); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot encode count of repeating group \"b\" in state: V0_D_1_BLOCK") + ); + throw; + } + }, std::logic_error); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsReEncodingLatestGroupField) +{ + MultipleGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(41); + MultipleGroups::B &bEncoder = encoder.bCount(1); + bEncoder.next(); + bEncoder.c(42); + MultipleGroups::D &dEncoder = encoder.dCount(1); + dEncoder.next(); + dEncoder.e(43); + EXPECT_THROW( + { + try + { + encoder.dCount(1); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot encode count of repeating group \"d\" in state: V0_D_1_BLOCK") + ); + throw; + } + }, std::logic_error); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedDecodingOfGroupField) +{ + MultipleGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(41); + MultipleGroups::B &bEncoder = encoder.bCount(1); + bEncoder.next(); + bEncoder.c(42); + MultipleGroups::D &dEncoder = encoder.dCount(1); + dEncoder.next(); + dEncoder.e(43); + encoder.checkEncodingIsComplete(); + + MultipleGroups decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleGroups::sbeBlockLength(), + MultipleGroups::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 41); + EXPECT_THROW( + { + try + { + decoder.d(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot decode count of repeating group \"d\" in state: V0_BLOCK") + ); + throw; + } + }, std::logic_error); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsReDecodingEarlierGroupField) +{ + MultipleGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(41); + MultipleGroups::B &bEncoder = encoder.bCount(1); + bEncoder.next(); + bEncoder.c(42); + MultipleGroups::D &dEncoder = encoder.dCount(1); + dEncoder.next(); + dEncoder.e(43); + encoder.checkEncodingIsComplete(); + + MultipleGroups decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleGroups::sbeBlockLength(), + MultipleGroups::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 41); + MultipleGroups::B &bGroup = decoder.b(); + EXPECT_EQ(bGroup.count(), 1u); + EXPECT_EQ(bGroup.next().c(), 42); + MultipleGroups::D &dGroup = decoder.d(); + EXPECT_EQ(dGroup.count(), 1u); + EXPECT_EQ(dGroup.next().e(), 43); + EXPECT_THROW( + { + try + { + decoder.b(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot decode count of repeating group \"b\" in state: V0_D_1_BLOCK") + ); + throw; + } + }, std::logic_error); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsReDecodingLatestGroupField) +{ + MultipleGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(41); + MultipleGroups::B &bEncoder = encoder.bCount(1); + bEncoder.next(); + bEncoder.c(42); + MultipleGroups::D &dEncoder = encoder.dCount(1); + dEncoder.next(); + dEncoder.e(43); + encoder.checkEncodingIsComplete(); + + MultipleGroups decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + MultipleGroups::sbeBlockLength(), + MultipleGroups::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 41); + MultipleGroups::B &bGroup = decoder.b(); + EXPECT_EQ(bGroup.count(), 1u); + EXPECT_EQ(bGroup.next().c(), 42); + MultipleGroups::D &dGroup = decoder.d(); + EXPECT_EQ(dGroup.count(), 1u); + EXPECT_EQ(dGroup.next().e(), 43); + EXPECT_THROW( + { + try + { + decoder.d(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot decode count of repeating group \"d\" in state: V0_D_1_BLOCK") + ); + throw; + } + }, std::logic_error); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedVarData) +{ + AddVarDataV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.putB("abc"); + encoder.checkEncodingIsComplete(); + + AddVarDataV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddVarDataV1::sbeBlockLength(), + AddVarDataV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + EXPECT_EQ(decoder.getBAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingAddedVarDataAsNullValue1) +{ + AddVarDataV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.checkEncodingIsComplete(); + + AddVarDataV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddVarDataV1::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + EXPECT_EQ(decoder.b(), nullptr); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingAddedVarDataAsNullValue2) +{ + AddVarDataV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.checkEncodingIsComplete(); + + AddVarDataV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddVarDataV1::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + EXPECT_EQ(decoder.getBAsString(), ""); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingAddedVarDataAsNullValue3) +{ + AddVarDataV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.checkEncodingIsComplete(); + + AddVarDataV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddVarDataV1::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + EXPECT_EQ(decoder.getBAsJsonEscapedString(), ""); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingAddedVarDataAsNullValue4) +{ + AddVarDataV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + encoder.checkEncodingIsComplete(); + + AddVarDataV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddVarDataV1::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 42); + char b[16]; + EXPECT_EQ(decoder.getB(b, 16), 0u); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingAsciiInsideGroupInSchemaDefinedOrder1) +{ + AsciiInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA("GBPUSD"); + AsciiInsideGroup::B &bGroup = encoder.bCount(1); + bGroup.next().putC("EURUSD"); + encoder.checkEncodingIsComplete(); + + AsciiInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AsciiInsideGroup::sbeBlockLength(), + AsciiInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.getAAsString(), "GBPUSD"); + AsciiInsideGroup::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().getCAsString(), "EURUSD"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingAsciiInsideGroupInSchemaDefinedOrder2) +{ + AsciiInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + std::string a("GBPUSD"); + encoder.putA(a); + AsciiInsideGroup::B &bGroup = encoder.bCount(1); + bGroup.next() + .c(0, 'E') + .c(1, 'U') + .c(2, 'R') + .c(3, 'U') + .c(4, 'S') + .c(5, 'D'); + encoder.checkEncodingIsComplete(); + + AsciiInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AsciiInsideGroup::sbeBlockLength(), + AsciiInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + char aOut[6]; + decoder.getA(aOut, 6); + EXPECT_STREQ(aOut, "GBPUSD"); + AsciiInsideGroup::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(0), 'E'); + EXPECT_EQ(b.c(1), 'U'); + EXPECT_EQ(b.c(2), 'R'); + EXPECT_EQ(b.c(3), 'U'); + EXPECT_EQ(b.c(4), 'S'); + EXPECT_EQ(b.c(5), 'D'); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsEncodingAsciiInsideGroupBeforeCallingNext1) +{ + AsciiInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA("GBPUSD"); + AsciiInsideGroup::B &bGroup = encoder.bCount(1); + EXPECT_THROW( + { + try + { + bGroup.putC("EURUSD"); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsEncodingAsciiInsideGroupBeforeCallingNext2) +{ + AsciiInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA("GBPUSD"); + AsciiInsideGroup::B &bGroup = encoder.bCount(1); + EXPECT_THROW( + { + try + { + const std::string c("EURUSD"); + bGroup.putC(c); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsEncodingAsciiInsideGroupBeforeCallingNext3) +{ + AsciiInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA("GBPUSD"); + AsciiInsideGroup::B &bGroup = encoder.bCount(1); + EXPECT_THROW( + { + try + { + bGroup.c(0, 'E') + .c(1, 'U') + .c(2, 'R') + .c(3, 'U') + .c(4, 'S') + .c(5, 'D'); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsDecodingAsciiInsideGroupBeforeCallingNext1) +{ + AsciiInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA("GBPUSD"); + AsciiInsideGroup::B &bGroup = encoder.bCount(1); + bGroup.next().putC("EURUSD"); + encoder.checkEncodingIsComplete(); + + AsciiInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AsciiInsideGroup::sbeBlockLength(), + AsciiInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.getAAsString(), "GBPUSD"); + AsciiInsideGroup::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_THROW( + { + try + { + b.getCAsString(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsDecodingAsciiInsideGroupBeforeCallingNext2) +{ + AsciiInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA("GBPUSD"); + AsciiInsideGroup::B &bGroup = encoder.bCount(1); + bGroup.next().putC("EURUSD"); + encoder.checkEncodingIsComplete(); + + AsciiInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AsciiInsideGroup::sbeBlockLength(), + AsciiInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.getAAsString(), "GBPUSD"); + AsciiInsideGroup::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_THROW( + { + try + { + b.c(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsDecodingAsciiInsideGroupBeforeCallingNext3) +{ + AsciiInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA("GBPUSD"); + AsciiInsideGroup::B &bGroup = encoder.bCount(1); + bGroup.next().putC("EURUSD"); + encoder.checkEncodingIsComplete(); + + AsciiInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AsciiInsideGroup::sbeBlockLength(), + AsciiInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.getAAsString(), "GBPUSD"); + AsciiInsideGroup::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_THROW( + { + try + { + char c[6]; + b.getC(c, 6); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsDecodingAsciiInsideGroupBeforeCallingNext4) +{ + AsciiInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA("GBPUSD"); + AsciiInsideGroup::B &bGroup = encoder.bCount(1); + bGroup.next().putC("EURUSD"); + encoder.checkEncodingIsComplete(); + + AsciiInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AsciiInsideGroup::sbeBlockLength(), + AsciiInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.getAAsString(), "GBPUSD"); + AsciiInsideGroup::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_THROW( + { + try + { + b.c(0); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsDecodingAsciiInsideGroupBeforeCallingNext5) +{ + AsciiInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA("GBPUSD"); + AsciiInsideGroup::B &bGroup = encoder.bCount(1); + bGroup.next().putC("EURUSD"); + encoder.checkEncodingIsComplete(); + + AsciiInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AsciiInsideGroup::sbeBlockLength(), + AsciiInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.getAAsString(), "GBPUSD"); + AsciiInsideGroup::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_THROW( + { + try + { + b.getCAsJsonEscapedString(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT( + e.what(), + HasSubstr("Cannot access field \"b.c\" in state: V0_B_N") + ); + throw; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, allowsReEncodingTopLevelAscii) +{ + AsciiInsideGroup encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA("GBPUSD"); + AsciiInsideGroup::B &bGroup = encoder.bCount(1); + bGroup.next().putC("EURUSD"); + + encoder.putA("CADUSD"); + encoder.checkEncodingIsComplete(); + + AsciiInsideGroup decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AsciiInsideGroup::sbeBlockLength(), + AsciiInsideGroup::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.getAAsString(), "CADUSD"); + AsciiInsideGroup::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + b.next(); + EXPECT_EQ(b.getCAsString(), "EURUSD"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedAsciiFieldBeforeGroup1) +{ + AddAsciiBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.putD("EURUSD"); + AddAsciiBeforeGroupV1::B &bGroup = encoder.bCount(1); + bGroup.next().c(2); + encoder.checkEncodingIsComplete(); + + AddAsciiBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddAsciiBeforeGroupV1::sbeBlockLength(), + AddAsciiBeforeGroupV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + EXPECT_EQ(decoder.getDAsString(), "EURUSD"); + AddAsciiBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedAsciiFieldBeforeGroup2) +{ + AddAsciiBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.putD("EURUSD"); + AddAsciiBeforeGroupV1::B &bGroup = encoder.bCount(1); + bGroup.next().c(2); + encoder.checkEncodingIsComplete(); + + AddAsciiBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddAsciiBeforeGroupV1::sbeBlockLength(), + AddAsciiBeforeGroupV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + const char *d = decoder.d(); + const std::string dAsString(d, 6); + ASSERT_EQ(dAsString, "EURUSD"); + AddAsciiBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedAsciiFieldBeforeGroup3) +{ + AddAsciiBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.putD("EURUSD"); + AddAsciiBeforeGroupV1::B &bGroup = encoder.bCount(1); + bGroup.next().c(2); + encoder.checkEncodingIsComplete(); + + AddAsciiBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddAsciiBeforeGroupV1::sbeBlockLength(), + AddAsciiBeforeGroupV1::sbeSchemaVersion(), + BUFFER_LEN + ); + const AddAsciiBeforeGroupV1 &constDecoder = decoder; + EXPECT_EQ(constDecoder.a(), 1); + const char *d = constDecoder.d(); + const std::string dAsString(d, 6); + ASSERT_EQ(dAsString, "EURUSD"); + AddAsciiBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedAsciiFieldBeforeGroup4) +{ + AddAsciiBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.putD("EURUSD"); + AddAsciiBeforeGroupV1::B &bGroup = encoder.bCount(1); + bGroup.next().c(2); + encoder.checkEncodingIsComplete(); + + AddAsciiBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddAsciiBeforeGroupV1::sbeBlockLength(), + AddAsciiBeforeGroupV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + char d[6]; + decoder.getD(d, 6); + const std::string dAsString(d, 6); + ASSERT_EQ(dAsString, "EURUSD"); + AddAsciiBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedAsciiFieldBeforeGroup5) +{ + AddAsciiBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.putD("EURUSD"); + AddAsciiBeforeGroupV1::B &bGroup = encoder.bCount(1); + bGroup.next().c(2); + encoder.checkEncodingIsComplete(); + + AddAsciiBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddAsciiBeforeGroupV1::sbeBlockLength(), + AddAsciiBeforeGroupV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + ASSERT_EQ(decoder.d(0), 'E'); + ASSERT_EQ(decoder.d(1), 'U'); + ASSERT_EQ(decoder.d(2), 'R'); + ASSERT_EQ(decoder.d(3), 'U'); + ASSERT_EQ(decoder.d(4), 'S'); + ASSERT_EQ(decoder.d(5), 'D'); + AddAsciiBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeAddedAsciiFieldBeforeGroup6) +{ + AddAsciiBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.putD("EURUSD"); + AddAsciiBeforeGroupV1::B &bGroup = encoder.bCount(1); + bGroup.next().c(2); + encoder.checkEncodingIsComplete(); + + AddAsciiBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddAsciiBeforeGroupV1::sbeBlockLength(), + AddAsciiBeforeGroupV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + ASSERT_EQ(decoder.dLength(), 6u); + AddAsciiBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingAsciiFieldBeforeGroupAsNullValue1) +{ + AddAsciiBeforeGroupV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + AddAsciiBeforeGroupV0::B &bGroup = encoder.bCount(1); + bGroup.next().c(2); + encoder.checkEncodingIsComplete(); + + AddAsciiBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddAsciiBeforeGroupV0::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + ASSERT_EQ(decoder.d(0), AddAsciiBeforeGroupV1::dNullValue()); + ASSERT_EQ(decoder.d(1), AddAsciiBeforeGroupV1::dNullValue()); + ASSERT_EQ(decoder.d(2), AddAsciiBeforeGroupV1::dNullValue()); + ASSERT_EQ(decoder.d(3), AddAsciiBeforeGroupV1::dNullValue()); + ASSERT_EQ(decoder.d(4), AddAsciiBeforeGroupV1::dNullValue()); + ASSERT_EQ(decoder.d(5), AddAsciiBeforeGroupV1::dNullValue()); + AddAsciiBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingAsciiFieldBeforeGroupAsNullValue2) +{ + AddAsciiBeforeGroupV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + AddAsciiBeforeGroupV0::B &bGroup = encoder.bCount(1); + bGroup.next().c(2); + encoder.checkEncodingIsComplete(); + + AddAsciiBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddAsciiBeforeGroupV0::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + char d[6]; + ASSERT_EQ(decoder.getD(d, 6), 0u); + AddAsciiBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToDecodeMissingAsciiFieldBeforeGroupAsNullValue3) +{ + AddAsciiBeforeGroupV0 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + AddAsciiBeforeGroupV0::B &bGroup = encoder.bCount(1); + bGroup.next().c(2); + encoder.checkEncodingIsComplete(); + + AddAsciiBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddAsciiBeforeGroupV0::sbeBlockLength(), + 0, + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + ASSERT_EQ(decoder.getDAsString(), ""); + AddAsciiBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsNewDecoderToSkipPresentButAddedAsciiFieldBeforeGroup) +{ + AddAsciiBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.putD("EURUSD"); + AddAsciiBeforeGroupV1::B &bGroup = encoder.bCount(1); + bGroup.next().c(2); + encoder.checkEncodingIsComplete(); + + AddAsciiBeforeGroupV1 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddAsciiBeforeGroupV1::sbeBlockLength(), + AddAsciiBeforeGroupV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddAsciiBeforeGroupV1::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsOldDecoderToSkipAddedAsciiFieldBeforeGroup) +{ + AddAsciiBeforeGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + encoder.putD("EURUSD"); + AddAsciiBeforeGroupV1::B &bGroup = encoder.bCount(1); + bGroup.next().c(2); + encoder.checkEncodingIsComplete(); + + AddAsciiBeforeGroupV0 decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + AddAsciiBeforeGroupV1::sbeBlockLength(), + AddAsciiBeforeGroupV1::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + AddAsciiBeforeGroupV0::B &b = decoder.b(); + EXPECT_EQ(b.count(), 1u); + EXPECT_EQ(b.next().c(), 2); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodeAndDecodeOfMessagesWithNoBlock) +{ + NoBlock encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.putA("abc"); + encoder.checkEncodingIsComplete(); + + NoBlock decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + NoBlock::sbeBlockLength(), + NoBlock::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.getAAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodeAndDecodeOfGroupsWithNoBlock) +{ + GroupWithNoBlock encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + GroupWithNoBlock::A &aGroup = encoder.aCount(1); + aGroup.next().putB("abc"); + encoder.checkEncodingIsComplete(); + + GroupWithNoBlock decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + GroupWithNoBlock::sbeBlockLength(), + GroupWithNoBlock::sbeSchemaVersion(), + BUFFER_LEN + ); + GroupWithNoBlock::A &a = decoder.a(); + EXPECT_EQ(a.count(), 1u); + EXPECT_EQ(a.next().getBAsString(), "abc"); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsEncodingElementOfEmptyGroup1) +{ + MultipleGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + MultipleGroups::B &bGroup = encoder.bCount(0); + MultipleGroups::D &dGroup = encoder.dCount(1); + dGroup.next().e(43); + + EXPECT_THROW( + { + try + { + bGroup.c(44); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr("Cannot access field \"b.c\" in state: V0_D_1_BLOCK")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsEncodingElementOfEmptyGroup2) +{ + NestedGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + NestedGroups::B &bGroup = encoder.bCount(1); + bGroup.next(); + bGroup.c(43); + NestedGroups::B::D &dGroup = bGroup.dCount(0); + bGroup.fCount(0); + encoder.hCount(0); + + EXPECT_THROW( + { + try + { + dGroup.e(44); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr("Cannot access field \"b.d.e\" in state: V0_H_DONE")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsEncodingElementOfEmptyGroup3) +{ + NestedGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + NestedGroups::B &bGroup = encoder.bCount(1); + bGroup.next(); + bGroup.c(43); + NestedGroups::B::D &dGroup = bGroup.dCount(0); + bGroup.fCount(0); + + EXPECT_THROW( + { + try + { + dGroup.e(44); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr("Cannot access field \"b.d.e\" in state: V0_B_1_F_DONE")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsEncodingElementOfEmptyGroup4) +{ + AddPrimitiveInsideGroupV1 encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + AddPrimitiveInsideGroupV1::B &bGroup = encoder.bCount(0); + + EXPECT_THROW( + { + try + { + bGroup.c(43); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr("Cannot access field \"b.c\" in state: V1_B_DONE")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsEncodingElementOfEmptyGroup5) +{ + GroupAndVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(42); + GroupAndVarLength::B &bGroup = encoder.bCount(0); + encoder.putD("abc"); + + EXPECT_THROW( + { + try + { + bGroup.c(43); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr("Cannot access field \"b.c\" in state: V0_D_DONE")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, allowsEncodingAndDecodingNestedGroupWithVarDataInSchemaDefinedOrder) +{ + NestedGroupWithVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + NestedGroupWithVarLength::B &bGroup = encoder.bCount(3); + bGroup.next().c(2).dCount(0); + bGroup.next().c(3).dCount(1).next().e(4).putF("abc"); + bGroup.next().c(5).dCount(2).next().e(6).putF("def").next().e(7).putF("ghi"); + encoder.checkEncodingIsComplete(); + + NestedGroupWithVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + NestedGroupWithVarLength::sbeBlockLength(), + NestedGroupWithVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + NestedGroupWithVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 3u); + EXPECT_EQ(bDecoder.next().c(), 2); + NestedGroupWithVarLength::B::D &dDecoder1 = bDecoder.d(); + EXPECT_EQ(dDecoder1.count(), 0u); + EXPECT_EQ(bDecoder.next().c(), 3); + NestedGroupWithVarLength::B::D &dDecoder2 = bDecoder.d(); + EXPECT_EQ(dDecoder2.count(), 1u); + EXPECT_EQ(dDecoder2.next().e(), 4); + EXPECT_EQ(dDecoder2.getFAsString(), "abc"); + EXPECT_EQ(bDecoder.next().c(), 5); + NestedGroupWithVarLength::B::D &dDecoder3 = bDecoder.d(); + EXPECT_EQ(dDecoder3.count(), 2u); + EXPECT_EQ(dDecoder3.next().e(), 6); + EXPECT_EQ(dDecoder3.getFAsString(), "def"); + EXPECT_EQ(dDecoder3.next().e(), 7); + EXPECT_EQ(dDecoder3.getFAsString(), "ghi"); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedEncodingOfVarLengthFieldInNestedGroupToNextInnerElement1) +{ + NestedGroupWithVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + NestedGroupWithVarLength::B &bGroup = encoder.bCount(1); + NestedGroupWithVarLength::B::D &dGroup = bGroup.next().c(5).dCount(2); + dGroup.next().e(7); + + EXPECT_THROW( + { + try + { + dGroup.next(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), + testing::HasSubstr( + "Cannot access next element in repeating group \"b.d\" in state: V0_B_1_D_N_BLOCK")); + EXPECT_THAT(e.what(), + testing::HasSubstr( + "Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedEncodingOfVarLengthFieldInNestedGroupToNextInnerElement2) +{ + NestedGroupWithVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + NestedGroupWithVarLength::B &bGroup = encoder.bCount(2); + NestedGroupWithVarLength::B::D &dGroup = bGroup.next().c(5).dCount(2); + dGroup.next().e(7); + + EXPECT_THROW( + { + try + { + dGroup.next(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), + testing::HasSubstr( + "Cannot access next element in repeating group \"b.d\" in state: V0_B_N_D_N_BLOCK")); + EXPECT_THAT(e.what(), + testing::HasSubstr( + "Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedDecodingOfVarLengthFieldInNestedGroupToNextInnerElement1) +{ + NestedGroupWithVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + NestedGroupWithVarLength::B &bGroup = encoder.bCount(1); + NestedGroupWithVarLength::B::D &dGroup = bGroup.next().c(2).dCount(2); + dGroup.next().e(3).putF("abc"); + dGroup.next().e(4).putF("def"); + encoder.checkEncodingIsComplete(); + + NestedGroupWithVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + NestedGroupWithVarLength::sbeBlockLength(), + NestedGroupWithVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + NestedGroupWithVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 1u); + EXPECT_EQ(bDecoder.next().c(), 2); + NestedGroupWithVarLength::B::D &dDecoder = bDecoder.d(); + EXPECT_EQ(dDecoder.count(), 2u); + EXPECT_EQ(dDecoder.next().e(), 3); + + EXPECT_THROW( + { + try + { + dDecoder.next(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), + testing::HasSubstr( + "Cannot access next element in repeating group \"b.d\" in state: V0_B_1_D_N_BLOCK.")); + EXPECT_THAT(e.what(), + testing::HasSubstr( + "Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedDecodingOfVarLengthFieldInNestedGroupToNextInnerElement2) +{ + NestedGroupWithVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + NestedGroupWithVarLength::B &bGroup = encoder.bCount(2); + NestedGroupWithVarLength::B::D &dGroup = bGroup.next().c(2).dCount(2); + dGroup.next().e(3).putF("abc"); + dGroup.next().e(4).putF("def"); + bGroup.next().c(5).dCount(0); + encoder.checkEncodingIsComplete(); + + NestedGroupWithVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + NestedGroupWithVarLength::sbeBlockLength(), + NestedGroupWithVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + NestedGroupWithVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 2); + NestedGroupWithVarLength::B::D &dDecoder = bDecoder.d(); + EXPECT_EQ(dDecoder.count(), 2u); + EXPECT_EQ(dDecoder.next().e(), 3); + + EXPECT_THROW( + { + try + { + dDecoder.next(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), + testing::HasSubstr( + "Cannot access next element in repeating group \"b.d\" in state: V0_B_N_D_N_BLOCK.")); + EXPECT_THAT(e.what(), + testing::HasSubstr( + "Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedDecodingOfVarLengthFieldInNestedGroupToNextOuterElement1) +{ + NestedGroupWithVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + NestedGroupWithVarLength::B &bGroup = encoder.bCount(2); + NestedGroupWithVarLength::B::D &dGroup = bGroup.next().c(2).dCount(2); + dGroup.next().e(3).putF("abc"); + dGroup.next().e(4).putF("def"); + bGroup.next().c(5).dCount(0); + encoder.checkEncodingIsComplete(); + + NestedGroupWithVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + NestedGroupWithVarLength::sbeBlockLength(), + NestedGroupWithVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + NestedGroupWithVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 2); + NestedGroupWithVarLength::B::D &dDecoder = bDecoder.d(); + EXPECT_EQ(dDecoder.count(), 2u); + EXPECT_EQ(dDecoder.next().e(), 3); + + EXPECT_THROW( + { + try + { + bDecoder.next(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), + testing::HasSubstr( + "Cannot access next element in repeating group \"b\" in state: V0_B_N_D_N_BLOCK.")); + EXPECT_THAT(e.what(), + testing::HasSubstr( + "Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsMissedDecodingOfVarLengthFieldInNestedGroupToNextOuterElement2) +{ + NestedGroupWithVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + NestedGroupWithVarLength::B &bGroup = encoder.bCount(2); + NestedGroupWithVarLength::B::D &dGroup = bGroup.next().c(2).dCount(1); + dGroup.next().e(3).putF("abc"); + bGroup.next().c(5).dCount(0); + encoder.checkEncodingIsComplete(); + + NestedGroupWithVarLength decoder; + decoder.wrapForDecode( + m_buffer, + OFFSET, + NestedGroupWithVarLength::sbeBlockLength(), + NestedGroupWithVarLength::sbeSchemaVersion(), + BUFFER_LEN + ); + EXPECT_EQ(decoder.a(), 1); + NestedGroupWithVarLength::B &bDecoder = decoder.b(); + EXPECT_EQ(bDecoder.count(), 2u); + EXPECT_EQ(bDecoder.next().c(), 2); + NestedGroupWithVarLength::B::D &dDecoder = bDecoder.d(); + EXPECT_EQ(dDecoder.count(), 1u); + EXPECT_EQ(dDecoder.next().e(), 3); + + EXPECT_THROW( + { + try + { + bDecoder.next(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), + testing::HasSubstr( + "Cannot access next element in repeating group \"b\" in state: V0_B_N_D_1_BLOCK.")); + EXPECT_THAT(e.what(), + testing::HasSubstr( + "Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsIncompleteMessagesDueToMissingVarLengthField1) +{ + MultipleVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1).putB("abc"); + + EXPECT_THROW( + { + try + { + encoder.checkEncodingIsComplete(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), + testing::HasSubstr( + std::string("Not fully encoded, current state: V0_B_DONE, ") + + "allowed transitions: \"cLength()\", \"c(?)\"")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsIncompleteMessagesDueToMissingVarLengthField2) +{ + NoBlock encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + + EXPECT_THROW( + { + try + { + encoder.checkEncodingIsComplete(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), + testing::HasSubstr( + std::string("Not fully encoded, current state: V0_BLOCK, ") + + "allowed transitions: \"aLength()\", \"a(?)\"")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsIncompleteMessagesDueToMissingTopLevelGroup1) +{ + MultipleGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1).bCount(0); + + EXPECT_THROW( + { + try + { + encoder.checkEncodingIsComplete(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr( + "Not fully encoded, current state: V0_B_DONE, allowed transitions:" + " \"b.resetCountToIndex()\", \"dCount(0)\", \"dCount(>0)\"")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsIncompleteMessagesDueToMissingTopLevelGroup2) +{ + MultipleGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1).bCount(1).next().c(2); + + EXPECT_THROW( + { + try + { + encoder.checkEncodingIsComplete(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr( + "Not fully encoded, current state: V0_B_1_BLOCK, allowed transitions: " + "\"b.c(?)\", \"b.resetCountToIndex()\", \"dCount(0)\", \"dCount(>0)\"")); + throw e; + } + }, + std::logic_error + ); +} + +TEST_F(FieldAccessOrderCheckTest, disallowsIncompleteMessagesDueToMissingTopLevelGroup3) +{ + MultipleGroups encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1); + + EXPECT_THROW( + { + try + { + encoder.checkEncodingIsComplete(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr( + "Not fully encoded, current state: V0_BLOCK, allowed transitions: " + "\"a(?)\", \"bCount(0)\", \"bCount(>0)\"")); + throw e; + } + }, + std::logic_error + ); +} + +class DisallowsIncompleteMessagesPart1Test : public FieldAccessOrderCheckTest, + public testing::WithParamInterface> +{ +}; + +TEST_P(DisallowsIncompleteMessagesPart1Test, disallowsIncompleteMessagesDueToMissingNestedGroup1) +{ + const auto bCount = std::get<0>(GetParam()); + const auto expectedState = std::get<1>(GetParam()); + + NestedGroupWithVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1).bCount(bCount).next().c(2); + + EXPECT_THROW( + { + try + { + encoder.checkEncodingIsComplete(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr( + "Not fully encoded, current state: " + expectedState)); + throw e; + } + }, + std::logic_error + ); +} + +INSTANTIATE_TEST_SUITE_P( + FieldAccessOrderCheckTest, + DisallowsIncompleteMessagesPart1Test, + testing::Values( + std::make_tuple(1, "V0_B_1_BLOCK"), + std::make_tuple(2, "V0_B_N_BLOCK") + ) +); + +class DisallowsIncompleteMessagesPart2Test : public FieldAccessOrderCheckTest, + public testing::WithParamInterface> +{ +}; + +TEST_P(DisallowsIncompleteMessagesPart2Test, disallowsIncompleteMessagesDueToMissingNestedGroup2) +{ + const auto bCount = std::get<0>(GetParam()); + const auto dCount = std::get<1>(GetParam()); + const auto expectedState = std::get<2>(GetParam()); + + NestedGroupWithVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1).bCount(bCount).next().c(2).dCount(dCount); + + EXPECT_THROW( + { + try + { + encoder.checkEncodingIsComplete(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr( + "Not fully encoded, current state: " + expectedState)); + throw e; + } + }, + std::logic_error + ); +} + +INSTANTIATE_TEST_SUITE_P( + FieldAccessOrderCheckTest, + DisallowsIncompleteMessagesPart2Test, + testing::Values( + std::make_tuple(1, 1, "V0_B_1_D_N"), + std::make_tuple(1, 2, "V0_B_1_D_N"), + std::make_tuple(2, 0, "V0_B_N_D_DONE"), + std::make_tuple(2, 1, "V0_B_N_D_N"), + std::make_tuple(2, 2, "V0_B_N_D_N") + ) +); + +class DisallowsIncompleteMessagesPart3Test : public FieldAccessOrderCheckTest, + public testing::WithParamInterface> +{ +}; + +TEST_P(DisallowsIncompleteMessagesPart3Test, disallowsIncompleteMessagesDueToMissingVarDataInNestedGroup) +{ + const auto bCount = std::get<0>(GetParam()); + const auto dCount = std::get<1>(GetParam()); + const auto expectedState = std::get<2>(GetParam()); + + NestedGroupWithVarLength encoder; + encoder.wrapForEncode(m_buffer, OFFSET, BUFFER_LEN); + encoder.a(1).bCount(bCount).next().c(2).dCount(dCount).next().e(10); + + EXPECT_THROW( + { + try + { + encoder.checkEncodingIsComplete(); + } + catch (const std::logic_error &e) + { + EXPECT_THAT(e.what(), testing::HasSubstr( + "Not fully encoded, current state: " + expectedState)); + throw e; + } + }, + std::logic_error + ); +} + +INSTANTIATE_TEST_SUITE_P( + FieldAccessOrderCheckTest, + DisallowsIncompleteMessagesPart3Test, + testing::Values( + std::make_tuple(1, 1, "V0_B_1_D_1_BLOCK"), + std::make_tuple(1, 2, "V0_B_1_D_N_BLOCK"), + std::make_tuple(2, 1, "V0_B_N_D_1_BLOCK"), + std::make_tuple(2, 2, "V0_B_N_D_N_BLOCK") + ) +); diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/FieldAccessOrderCheckTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/FieldAccessOrderCheckTest.java new file mode 100644 index 0000000000..97d51b199c --- /dev/null +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/FieldAccessOrderCheckTest.java @@ -0,0 +1,3425 @@ +/* + * Copyright 2013-2023 Real Logic Limited. + * + * 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 + * + * https://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. + */ + +package uk.co.real_logic.sbe; + +import order.check.*; +import org.agrona.ExpandableArrayBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.nio.charset.StandardCharsets; +import java.util.Random; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + +public class FieldAccessOrderCheckTest +{ + private static final Class INCORRECT_ORDER_EXCEPTION_CLASS = IllegalStateException.class; + private static final int OFFSET = 0; + private final MutableDirectBuffer buffer = new ExpandableArrayBuffer(); + private final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder(); + private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder(); + + @BeforeAll + static void assumeDebugMode() + { + final boolean productionMode = Boolean.getBoolean("agrona.disable.bounds.checks"); + assumeFalse(productionMode); + } + + @BeforeEach + void setUp() + { + new Random().nextBytes(buffer.byteArray()); + } + + @Test + void allowsEncodingAndDecodingVariableLengthFieldsInSchemaDefinedOrder() + { + final MultipleVarLengthEncoder encoder = new MultipleVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.b("abc"); + encoder.c("def"); + encoder.checkEncodingIsComplete(); + + final MultipleVarLengthDecoder decoder = new MultipleVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + assertThat(decoder.b(), equalTo("abc")); + assertThat(decoder.c(), equalTo("def")); + assertThat(decoder.toString(), containsString("a=42|b='abc'|c='def'")); + } + + @Test + void allowsDecodingVariableLengthFieldsAfterRewind() + { + final MultipleVarLengthEncoder encoder = new MultipleVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.b("abc"); + encoder.c("def"); + encoder.checkEncodingIsComplete(); + + final MultipleVarLengthDecoder decoder = new MultipleVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + assertThat(decoder.b(), equalTo("abc")); + assertThat(decoder.c(), equalTo("def")); + assertThat(decoder.toString(), containsString("a=42|b='abc'|c='def'")); + + decoder.sbeRewind(); + + assertThat(decoder.a(), equalTo(42)); + assertThat(decoder.b(), equalTo("abc")); + assertThat(decoder.c(), equalTo("def")); + assertThat(decoder.toString(), containsString("a=42|b='abc'|c='def'")); + } + + @Test + void allowsDecodingToSkipVariableLengthFields() + { + final MultipleVarLengthEncoder encoder = new MultipleVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.b("abc"); + encoder.c("def"); + encoder.checkEncodingIsComplete(); + + final MultipleVarLengthDecoder decoder = new MultipleVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + assertThat(decoder.skipB(), equalTo(3)); + assertThat(decoder.c(), equalTo("def")); + assertThat(decoder.toString(), containsString("a=42|b='abc'|c='def'")); + } + + @Test + void allowsReEncodingTopLevelPrimitiveFields() + { + final MultipleVarLengthEncoder encoder = new MultipleVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.b("abc"); + encoder.c("def"); + encoder.a(43); + encoder.checkEncodingIsComplete(); + + final MultipleVarLengthDecoder decoder = new MultipleVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(43)); + assertThat(decoder.b(), equalTo("abc")); + assertThat(decoder.c(), equalTo("def")); + } + + @Test + void disallowsSkippingEncodingOfVariableLengthField1() + { + final MultipleVarLengthEncoder encoder = new MultipleVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> encoder.c("def")); + assertThat(exception.getMessage(), containsString("Cannot access field \"c\" in state: V0_BLOCK")); + } + + @Test + void disallowsSkippingEncodingOfVariableLengthField2() + { + final MultipleVarLengthEncoder encoder = new MultipleVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final CharSequence def = new StringBuilder("def"); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> encoder.c(def)); + assertThat(exception.getMessage(), containsString("Cannot access field \"c\" in state: V0_BLOCK")); + } + + @Test + void disallowsSkippingEncodingOfVariableLengthField3() + { + final MultipleVarLengthEncoder encoder = new MultipleVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final byte[] value = "def".getBytes(StandardCharsets.US_ASCII); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> encoder.putC(value, 0, value.length)); + assertThat(exception.getMessage(), containsString("Cannot access field \"c\" in state: V0_BLOCK")); + } + + @Test + void disallowsSkippingEncodingOfVariableLengthField4() + { + final MultipleVarLengthEncoder encoder = new MultipleVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final byte[] value = "def".getBytes(StandardCharsets.US_ASCII); + final UnsafeBuffer buffer = new UnsafeBuffer(value); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> encoder.putC(buffer, 0, buffer.capacity())); + assertThat(exception.getMessage(), containsString("Cannot access field \"c\" in state: V0_BLOCK")); + } + + @Test + void disallowsReEncodingEarlierVariableLengthFields() + { + final MultipleVarLengthEncoder encoder = new MultipleVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.b("abc"); + encoder.c("def"); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> encoder.b("ghi")); + assertThat(exception.getMessage(), containsString("Cannot access field \"b\" in state: V0_C_DONE")); + } + + @Test + void disallowsReEncodingLatestVariableLengthField() + { + final MultipleVarLengthEncoder encoder = new MultipleVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.b("abc"); + encoder.c("def"); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> encoder.c("ghi")); + assertThat(exception.getMessage(), containsString("Cannot access field \"c\" in state: V0_C_DONE")); + } + + @Test + void disallowsSkippingDecodingOfVariableLengthField1() + { + final MultipleVarLengthDecoder decoder = decodeUntilVarLengthFields(); + + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, decoder::c); + assertThat(exception.getMessage(), containsString("Cannot access field \"c\" in state: V0_BLOCK")); + } + + @Test + void disallowsSkippingDecodingOfVariableLengthField2() + { + final MultipleVarLengthDecoder decoder = decodeUntilVarLengthFields(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> decoder.wrapC(new UnsafeBuffer())); + assertThat(exception.getMessage(), containsString("Cannot access field \"c\" in state: V0_BLOCK")); + } + + @Test + void disallowsSkippingDecodingOfVariableLengthField3() + { + final MultipleVarLengthDecoder decoder = decodeUntilVarLengthFields(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> decoder.getC(new StringBuilder())); + assertThat(exception.getMessage(), containsString("Cannot access field \"c\" in state: V0_BLOCK")); + } + + @Test + void disallowsSkippingDecodingOfVariableLengthField4() + { + final MultipleVarLengthDecoder decoder = decodeUntilVarLengthFields(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> decoder.getC(new byte[3], 0, 3)); + assertThat(exception.getMessage(), containsString("Cannot access field \"c\" in state: V0_BLOCK")); + } + + @Test + void disallowsSkippingDecodingOfVariableLengthField5() + { + final MultipleVarLengthDecoder decoder = decodeUntilVarLengthFields(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> decoder.getC(new ExpandableArrayBuffer(), 0, 3)); + assertThat(exception.getMessage(), containsString("Cannot access field \"c\" in state: V0_BLOCK")); + } + + @Test + void allowsRepeatedDecodingOfVariableLengthDataLength() + { + final MultipleVarLengthDecoder decoder = decodeUntilVarLengthFields(); + assertThat(decoder.bLength(), equalTo(3)); + assertThat(decoder.bLength(), equalTo(3)); + assertThat(decoder.bLength(), equalTo(3)); + assertThat(decoder.b(), equalTo("abc")); + assertThat(decoder.cLength(), equalTo(3)); + assertThat(decoder.cLength(), equalTo(3)); + assertThat(decoder.cLength(), equalTo(3)); + } + + @Test + void disallowsReDecodingEarlierVariableLengthField() + { + final MultipleVarLengthDecoder decoder = decodeUntilVarLengthFields(); + assertThat(decoder.b(), equalTo("abc")); + assertThat(decoder.c(), equalTo("def")); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, decoder::b); + assertThat(exception.getMessage(), containsString("Cannot access field \"b\" in state: V0_C_DONE")); + } + + @Test + void disallowsReDecodingLatestVariableLengthField() + { + final MultipleVarLengthDecoder decoder = decodeUntilVarLengthFields(); + assertThat(decoder.b(), equalTo("abc")); + assertThat(decoder.c(), equalTo("def")); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, decoder::c); + assertThat(exception.getMessage(), containsString("Cannot access field \"c\" in state: V0_C_DONE")); + } + + private MultipleVarLengthDecoder decodeUntilVarLengthFields() + { + final MultipleVarLengthEncoder encoder = new MultipleVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.b("abc"); + encoder.c("def"); + encoder.checkEncodingIsComplete(); + + final MultipleVarLengthDecoder decoder = new MultipleVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + return decoder; + } + + @Test + void allowsEncodingAndDecodingGroupAndVariableLengthFieldsInSchemaDefinedOrder() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2) + .next() + .c(1) + .next() + .c(2); + encoder.d("abc"); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final GroupAndVarLengthDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(2)); + assertThat(bs.next().c(), equalTo(1)); + assertThat(bs.next().c(), equalTo(2)); + assertThat(decoder.d(), equalTo("abc")); + assertThat(decoder.toString(), containsString("a=42|b=[(c=1),(c=2)]|d='abc'")); + } + + @Test + void allowsEncoderToResetZeroGroupLengthToZero() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(0).resetCountToIndex(); + encoder.d("abc"); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final GroupAndVarLengthDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(0)); + assertThat(decoder.d(), equalTo("abc")); + assertThat(decoder.toString(), containsString("a=42|b=[]|d='abc'")); + } + + @Test + void allowsEncoderToResetNonZeroGroupLengthToZeroBeforeCallingNext() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2).resetCountToIndex(); + encoder.d("abc"); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final GroupAndVarLengthDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(0)); + assertThat(decoder.d(), equalTo("abc")); + assertThat(decoder.toString(), containsString("a=42|b=[]|d='abc'")); + } + + @Test + void allowsEncoderToResetNonZeroGroupLengthToNonZero() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2).next().c(43).resetCountToIndex(); + encoder.d("abc"); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final GroupAndVarLengthDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(1)); + assertThat(bs.next().c(), equalTo(43)); + assertThat(decoder.d(), equalTo("abc")); + assertThat(decoder.toString(), containsString("a=42|b=[(c=43)]|d='abc'")); + } + + @Test + void disallowsEncoderToResetGroupLengthMidGroupElement() + { + final NestedGroupsEncoder encoder = new NestedGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final NestedGroupsEncoder.BEncoder bEncoder = encoder.bCount(2).next().c(43); + final IllegalStateException exception = assertThrows(IllegalStateException.class, bEncoder::resetCountToIndex); + assertThat(exception.getMessage(), + containsString("Cannot reset count of repeating group \"b\" in state: V0_B_N_BLOCK")); + } + + @Test + void allowsDecodingGroupAndVariableLengthFieldsAfterRewind() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2) + .next() + .c(1) + .next() + .c(2); + encoder.d("abc"); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final GroupAndVarLengthDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(2)); + assertThat(bs.next().c(), equalTo(1)); + assertThat(bs.next().c(), equalTo(2)); + assertThat(decoder.d(), equalTo("abc")); + assertThat(decoder.toString(), containsString("a=42|b=[(c=1),(c=2)]|d='abc'")); + + decoder.sbeRewind(); + + assertThat(decoder.a(), equalTo(42)); + final GroupAndVarLengthDecoder.BDecoder bs2 = decoder.b(); + assertThat(bs2.count(), equalTo(2)); + assertThat(bs2.next().c(), equalTo(1)); + assertThat(bs2.next().c(), equalTo(2)); + assertThat(decoder.d(), equalTo("abc")); + assertThat(decoder.toString(), containsString("a=42|b=[(c=1),(c=2)]|d='abc'")); + } + + @Test + void allowsDecodingToSkipMessage() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2) + .next() + .c(1) + .next() + .c(2); + encoder.d("abc"); + encoder.checkEncodingIsComplete(); + + final int nextEncodeOffset = encoder.limit(); + encoder.wrapAndApplyHeader(buffer, nextEncodeOffset, messageHeaderEncoder); + encoder.a(43); + encoder.bCount(2) + .next() + .c(3) + .next() + .c(4); + encoder.d("def"); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + + decoder.sbeSkip(); + final int nextDecodeOffset = decoder.limit(); + assertThat(nextDecodeOffset, equalTo(nextEncodeOffset)); + + decoder.wrapAndApplyHeader(buffer, nextDecodeOffset, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(43)); + final GroupAndVarLengthDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(2)); + assertThat(bs.next().c(), equalTo(3)); + assertThat(bs.next().c(), equalTo(4)); + assertThat(decoder.d(), equalTo("def")); + } + + @Test + void allowsDecodingToDetermineMessageLengthBeforeReadingFields() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(43); + encoder.bCount(2) + .next() + .c(3) + .next() + .c(4); + encoder.d("def"); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + + assertThat(decoder.sbeDecodedLength(), equalTo(18)); + assertThat(decoder.a(), equalTo(43)); + final GroupAndVarLengthDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(2)); + assertThat(bs.next().c(), equalTo(3)); + assertThat(bs.next().c(), equalTo(4)); + assertThat(decoder.d(), equalTo("def")); + } + + @Test + void allowsEncodingAndDecodingEmptyGroupAndVariableLengthFieldsInSchemaDefinedOrder() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(0); + encoder.d("abc"); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final GroupAndVarLengthDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(0)); + assertThat(decoder.d(), equalTo("abc")); + assertThat(decoder.toString(), containsString("a=42|b=[]|d='abc'")); + } + + @Test + @Disabled("Our access checks are too strict to allow the behaviour in this test.") + void allowsReEncodingPrimitiveFieldInGroupElementAfterTopLevelVariableLengthField() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final GroupAndVarLengthEncoder.BEncoder bEncoder = encoder.bCount(2); + bEncoder + .next() + .c(1) + .next() + .c(2); + encoder.d("abc"); + bEncoder.c(3); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final GroupAndVarLengthDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(2)); + assertThat(bs.next().c(), equalTo(1)); + assertThat(bs.next().c(), equalTo(3)); + assertThat(decoder.d(), equalTo("abc")); + } + + @Test + @Disabled("Our access checks are too strict to allow the behaviour in this test.") + void allowsReWrappingGroupDecoderAfterAccessingGroupCount() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final GroupAndVarLengthEncoder.BEncoder bEncoder = encoder.bCount(2); + bEncoder + .next() + .c(1) + .next() + .c(2); + encoder.d("abc"); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + assertThat(decoder.b().count(), equalTo(2)); + final GroupAndVarLengthDecoder.BDecoder b = decoder.b(); + assertThat(b.next().c(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + assertThat(decoder.d(), equalTo("abc")); + } + + @Test + void disallowsEncodingGroupElementBeforeCallingNext() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final GroupAndVarLengthEncoder.BEncoder bEncoder = encoder.bCount(1); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> bEncoder.c(1)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsDecodingGroupElementBeforeCallingNext() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2) + .next() + .c(1) + .next() + .c(2); + encoder.d("abc"); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final GroupAndVarLengthDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(2)); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, bs::c); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsSkippingEncodingOfGroup() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> encoder.d("abc")); + assertThat(exception.getMessage(), containsString("Cannot access field \"d\" in state: V0_BLOCK")); + } + + @Test + void disallowsReEncodingVariableLengthFieldAfterGroup() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2) + .next() + .c(1) + .next() + .c(2); + encoder.d("abc"); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> encoder.d("def")); + assertThat(exception.getMessage(), containsString("Cannot access field \"d\" in state: V0_D_DONE")); + } + + @Test + void disallowsReEncodingGroupCount() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2) + .next() + .c(1) + .next() + .c(2); + encoder.d("abc"); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> encoder.bCount(1)); + assertThat(exception.getMessage(), + containsString("Cannot encode count of repeating group \"b\" in state: V0_D_DONE")); + } + + @Test + @Disabled("Our access checks are too strict to allow the behaviour in this test.") + void allowsReEncodingGroupElementBlockFieldAfterTopLevelVariableLengthField() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final GroupAndVarLengthEncoder.BEncoder b = encoder.bCount(2) + .next() + .c(1) + .next() + .c(2); + encoder.d("abc"); + b.c(3); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final GroupAndVarLengthDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(2)); + assertThat(bs.next().c(), equalTo(1)); + assertThat(bs.next().c(), equalTo(3)); + assertThat(decoder.d(), equalTo("abc")); + } + + @Test + void disallowsMissedDecodingOfGroupBeforeVariableLengthField() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2) + .next() + .c(1) + .next() + .c(2); + encoder.d("abc"); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, decoder::d); + assertThat(exception.getMessage(), containsString("Cannot access field \"d\" in state: V0_BLOCK")); + } + + @Test + void disallowsReDecodingVariableLengthFieldAfterGroup() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2) + .next() + .c(1) + .next() + .c(2); + encoder.d("abc"); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final GroupAndVarLengthDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(2)); + assertThat(bs.next().c(), equalTo(1)); + assertThat(bs.next().c(), equalTo(2)); + assertThat(decoder.d(), equalTo("abc")); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, decoder::d); + assertThat(exception.getMessage(), containsString("Cannot access field \"d\" in state: V0_D_DONE")); + } + + @Test + void disallowsReDecodingGroupAfterVariableLengthField() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2) + .next() + .c(1) + .next() + .c(2); + encoder.d("abc"); + encoder.checkEncodingIsComplete(); + + final GroupAndVarLengthDecoder decoder = new GroupAndVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final GroupAndVarLengthDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(2)); + assertThat(bs.next().c(), equalTo(1)); + assertThat(bs.next().c(), equalTo(2)); + assertThat(decoder.d(), equalTo("abc")); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, decoder::b); + assertThat(exception.getMessage(), + containsString("Cannot decode count of repeating group \"b\" in state: V0_D_DONE")); + } + + @Test + void allowsEncodingAndDecodingVariableLengthFieldInsideGroupInSchemaDefinedOrder() + { + final VarLengthInsideGroupEncoder encoder = new VarLengthInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2) + .next() + .c(1) + .d("abc") + .next() + .c(2) + .d("def"); + encoder.e("ghi"); + encoder.checkEncodingIsComplete(); + + final VarLengthInsideGroupDecoder decoder = new VarLengthInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final VarLengthInsideGroupDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(2)); + assertThat(bs.next().c(), equalTo(1)); + assertThat(bs.d(), equalTo("abc")); + assertThat(bs.next().c(), equalTo(2)); + assertThat(bs.d(), equalTo("def")); + assertThat(decoder.e(), equalTo("ghi")); + assertThat(decoder.toString(), containsString("a=42|b=[(c=1|d='abc'),(c=2|d='def')]|e='ghi'")); + } + + @Test + @Disabled("Our access checks are too strict to allow the behaviour in this test.") + void allowsReEncodingGroupElementPrimitiveFieldAfterElementVariableLengthField() + { + final VarLengthInsideGroupEncoder encoder = new VarLengthInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final VarLengthInsideGroupEncoder.BEncoder bEncoder = encoder.bCount(1); + bEncoder + .next() + .c(1) + .d("abc"); + bEncoder.c(2); + encoder.e("ghi"); + encoder.checkEncodingIsComplete(); + + final VarLengthInsideGroupDecoder decoder = new VarLengthInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final VarLengthInsideGroupDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(1)); + assertThat(bs.next().c(), equalTo(2)); + assertThat(bs.d(), equalTo("abc")); + assertThat(decoder.e(), equalTo("ghi")); + } + + @Test + void disallowsMissedGroupElementVariableLengthFieldToEncodeAtTopLevel() + { + final VarLengthInsideGroupEncoder encoder = new VarLengthInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(1).next().c(1); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> encoder.e("abc")); + assertThat(exception.getMessage(), containsString("Cannot access field \"e\" in state: V0_B_1_BLOCK")); + } + + @Test + void disallowsMissedGroupElementVariableLengthFieldToEncodeNextElement() + { + final VarLengthInsideGroupEncoder encoder = new VarLengthInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final VarLengthInsideGroupEncoder.BEncoder b = encoder.bCount(2) + .next(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, b::next); + assertThat(exception.getMessage(), + containsString("Cannot access next element in repeating group \"b\" in state: V0_B_N_BLOCK")); + } + + @Test + void disallowsMissedGroupElementEncoding() + { + final VarLengthInsideGroupEncoder encoder = new VarLengthInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2) + .next() + .c(1) + .d("abc"); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> encoder.e("abc")); + assertThat(exception.getMessage(), containsString("Cannot access field \"e\" in state: V0_B_N_D_DONE")); + } + + @Test + void disallowsReEncodingGroupElementVariableLengthField() + { + final VarLengthInsideGroupEncoder encoder = new VarLengthInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final VarLengthInsideGroupEncoder.BEncoder b = encoder.bCount(1) + .next() + .c(1) + .d("abc"); + encoder.e("def"); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> b.d("ghi")); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.d\" in state: V0_E_DONE")); + } + + @Test + void disallowsReDecodingGroupElementVariableLengthField() + { + final VarLengthInsideGroupEncoder encoder = new VarLengthInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2) + .next() + .c(1) + .d("abc") + .next() + .c(2) + .d("def"); + encoder.e("ghi"); + encoder.checkEncodingIsComplete(); + + final VarLengthInsideGroupDecoder decoder = new VarLengthInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final VarLengthInsideGroupDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(2)); + assertThat(bs.next().c(), equalTo(1)); + assertThat(bs.d(), equalTo("abc")); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, bs::d); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.d\" in state: V0_B_N_D_DONE")); + } + + @Test + void disallowsMissedDecodingOfGroupElementVariableLengthFieldToNextElement() + { + final VarLengthInsideGroupEncoder encoder = new VarLengthInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2) + .next() + .c(1) + .d("abc") + .next() + .c(2) + .d("def"); + encoder.e("ghi"); + encoder.checkEncodingIsComplete(); + + final VarLengthInsideGroupDecoder decoder = new VarLengthInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final VarLengthInsideGroupDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(2)); + assertThat(bs.next().c(), equalTo(1)); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, bs::next); + assertThat(exception.getMessage(), + containsString("Cannot access next element in repeating group \"b\" in state: V0_B_N_BLOCK")); + } + + @Test + void disallowsMissedDecodingOfGroupElementVariableLengthFieldToTopLevel() + { + final VarLengthInsideGroupEncoder encoder = new VarLengthInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(1) + .next() + .c(1) + .d("abc"); + encoder.e("ghi"); + encoder.checkEncodingIsComplete(); + + final VarLengthInsideGroupDecoder decoder = new VarLengthInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final VarLengthInsideGroupDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(1)); + assertThat(bs.next().c(), equalTo(1)); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, decoder::e); + assertThat(exception.getMessage(), containsString("Cannot access field \"e\" in state: V0_B_1_BLOCK")); + } + + @Test + void disallowsMissedDecodingOfGroupElement() + { + final VarLengthInsideGroupEncoder encoder = new VarLengthInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(2) + .next() + .c(1) + .d("abc") + .next() + .c(2) + .d("def"); + encoder.e("ghi"); + encoder.checkEncodingIsComplete(); + + final VarLengthInsideGroupDecoder decoder = new VarLengthInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final VarLengthInsideGroupDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(2)); + assertThat(bs.next().c(), equalTo(1)); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, decoder::e); + assertThat(exception.getMessage(), containsString("Cannot access field \"e\" in state: V0_B_N_BLOCK")); + } + + @Test + void allowsEncodingNestedGroupsInSchemaDefinedOrder() + { + final NestedGroupsEncoder encoder = new NestedGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final NestedGroupsEncoder.BEncoder b = encoder.bCount(2) + .next(); + b.c(1); + b.dCount(2) + .next() + .e(2) + .next() + .e(3); + b.fCount(1) + .next() + .g(4); + b.next(); + b.c(5); + b.dCount(1) + .next() + .e(6); + b.fCount(1) + .next() + .g(7); + encoder.hCount(1) + .next() + .i(8); + encoder.checkEncodingIsComplete(); + + final NestedGroupsDecoder decoder = new NestedGroupsDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final NestedGroupsDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(2)); + final NestedGroupsDecoder.BDecoder b0 = bs.next(); + assertThat(b0.c(), equalTo(1)); + final NestedGroupsDecoder.BDecoder.DDecoder b0ds = b0.d(); + assertThat(b0ds.count(), equalTo(2)); + assertThat(b0ds.next().e(), equalTo(2)); + assertThat(b0ds.next().e(), equalTo(3)); + final NestedGroupsDecoder.BDecoder.FDecoder b0fs = b0.f(); + assertThat(b0fs.count(), equalTo(1)); + assertThat(b0fs.next().g(), equalTo(4)); + final NestedGroupsDecoder.BDecoder b1 = bs.next(); + assertThat(b1.c(), equalTo(5)); + final NestedGroupsDecoder.BDecoder.DDecoder b1ds = b1.d(); + assertThat(b1ds.count(), equalTo(1)); + assertThat(b1ds.next().e(), equalTo(6)); + final NestedGroupsDecoder.BDecoder.FDecoder b1fs = b1.f(); + assertThat(b1fs.count(), equalTo(1)); + assertThat(b1fs.next().g(), equalTo(7)); + final NestedGroupsDecoder.HDecoder hs = decoder.h(); + assertThat(hs.count(), equalTo(1)); + assertThat(hs.next().i(), equalTo(8)); + assertThat(decoder.toString(), + containsString("a=42|b=[(c=1|d=[(e=2),(e=3)]|f=[(g=4)]),(c=5|d=[(e=6)]|f=[(g=7)])]|h=[(i=8)]")); + } + + @Test + void allowsEncodingEmptyNestedGroupsInSchemaDefinedOrder() + { + final NestedGroupsEncoder encoder = new NestedGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(0); + encoder.hCount(0); + encoder.checkEncodingIsComplete(); + + final NestedGroupsDecoder decoder = new NestedGroupsDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + final NestedGroupsDecoder.BDecoder bs = decoder.b(); + assertThat(bs.count(), equalTo(0)); + final NestedGroupsDecoder.HDecoder hs = decoder.h(); + assertThat(hs.count(), equalTo(0)); + assertThat(decoder.toString(), containsString("a=42|b=[]|h=[]")); + } + + @Test + void disallowsMissedEncodingOfNestedGroup() + { + final NestedGroupsEncoder encoder = new NestedGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final NestedGroupsEncoder.BEncoder b = encoder.bCount(1) + .next() + .c(1); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> b.fCount(1)); + assertThat(exception.getMessage(), + containsString("Cannot encode count of repeating group \"b.f\" in state: V0_B_1_BLOCK")); + } + + @Test + void allowsEncodingAndDecodingCompositeInsideGroupInSchemaDefinedOrder() + { + final CompositeInsideGroupEncoder encoder = new CompositeInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a().x(1).y(2); + encoder.bCount(1) + .next() + .c().x(3).y(4); + encoder.checkEncodingIsComplete(); + + final CompositeInsideGroupDecoder decoder = new CompositeInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final PointDecoder a = decoder.a(); + assertThat(a.x(), equalTo(1)); + assertThat(a.y(), equalTo(2)); + final CompositeInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + final PointDecoder c = b.next().c(); + assertThat(c.x(), equalTo(3)); + assertThat(c.y(), equalTo(4)); + assertThat(decoder.toString(), containsString("a=(x=1|y=2)|b=[(c=(x=3|y=4))]")); + } + + @Test + void disallowsEncodingCompositeInsideGroupBeforeCallingNext() + { + final CompositeInsideGroupEncoder encoder = new CompositeInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a().x(1).y(2); + final CompositeInsideGroupEncoder.BEncoder bEncoder = encoder.bCount(1); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, bEncoder::c); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsDecodingCompositeInsideGroupBeforeCallingNext() + { + final CompositeInsideGroupEncoder encoder = new CompositeInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a().x(1).y(2); + encoder.bCount(1) + .next() + .c().x(3).y(4); + + final CompositeInsideGroupDecoder decoder = new CompositeInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final PointDecoder a = decoder.a(); + assertThat(a.x(), equalTo(1)); + assertThat(a.y(), equalTo(2)); + final CompositeInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, b::c); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void allowsReEncodingTopLevelCompositeViaReWrap() + { + final CompositeInsideGroupEncoder encoder = new CompositeInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a().x(1).y(2); + encoder.bCount(1) + .next() + .c().x(3).y(4); + encoder.a().x(5).y(6); + encoder.checkEncodingIsComplete(); + + final CompositeInsideGroupDecoder decoder = new CompositeInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final PointDecoder a = decoder.a(); + assertThat(a.x(), equalTo(5)); + assertThat(a.y(), equalTo(6)); + final CompositeInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + final PointDecoder c = b.next().c(); + assertThat(c.x(), equalTo(3)); + assertThat(c.y(), equalTo(4)); + } + + @Test + void allowsReEncodingTopLevelCompositeViaEncoderReference() + { + final CompositeInsideGroupEncoder encoder = new CompositeInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + final PointEncoder aEncoder = encoder.a(); + aEncoder.x(1).y(2); + encoder.bCount(1) + .next() + .c().x(3).y(4); + aEncoder.x(5).y(6); + encoder.checkEncodingIsComplete(); + + final CompositeInsideGroupDecoder decoder = new CompositeInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final PointDecoder a = decoder.a(); + assertThat(a.x(), equalTo(5)); + assertThat(a.y(), equalTo(6)); + final CompositeInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + final PointDecoder c = b.next().c(); + assertThat(c.x(), equalTo(3)); + assertThat(c.y(), equalTo(4)); + } + + @Test + void allowsReEncodingGroupElementCompositeViaReWrap() + { + final CompositeInsideGroupEncoder encoder = new CompositeInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a().x(1).y(2); + final CompositeInsideGroupEncoder.BEncoder bEncoder = encoder.bCount(1).next(); + bEncoder.c().x(3).y(4); + bEncoder.c().x(5).y(6); + encoder.checkEncodingIsComplete(); + + final CompositeInsideGroupDecoder decoder = new CompositeInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final PointDecoder a = decoder.a(); + assertThat(a.x(), equalTo(1)); + assertThat(a.y(), equalTo(2)); + final CompositeInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + final PointDecoder c = b.next().c(); + assertThat(c.x(), equalTo(5)); + assertThat(c.y(), equalTo(6)); + } + + @Test + void allowsReEncodingGroupElementCompositeViaEncoderReference() + { + final CompositeInsideGroupEncoder encoder = new CompositeInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a().x(1).y(2); + final CompositeInsideGroupEncoder.BEncoder bEncoder = encoder.bCount(1).next(); + final PointEncoder cEncoder = bEncoder.c(); + cEncoder.x(3).y(4); + cEncoder.x(5).y(6); + encoder.checkEncodingIsComplete(); + + final CompositeInsideGroupDecoder decoder = new CompositeInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final PointDecoder a = decoder.a(); + assertThat(a.x(), equalTo(1)); + assertThat(a.y(), equalTo(2)); + final CompositeInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + final PointDecoder c = b.next().c(); + assertThat(c.x(), equalTo(5)); + assertThat(c.y(), equalTo(6)); + } + + @Test + void allowsReDecodingTopLevelCompositeViaReWrap() + { + final CompositeInsideGroupEncoder encoder = new CompositeInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a().x(1).y(2); + encoder.bCount(1) + .next() + .c().x(3).y(4); + encoder.checkEncodingIsComplete(); + + final CompositeInsideGroupDecoder decoder = new CompositeInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final PointDecoder a1 = decoder.a(); + assertThat(a1.x(), equalTo(1)); + assertThat(a1.y(), equalTo(2)); + final CompositeInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + final PointDecoder c = b.next().c(); + assertThat(c.x(), equalTo(3)); + assertThat(c.y(), equalTo(4)); + final PointDecoder a2 = decoder.a(); + assertThat(a2.x(), equalTo(1)); + assertThat(a2.y(), equalTo(2)); + } + + @Test + void allowsReDecodingTopLevelCompositeViaEncoderReference() + { + final CompositeInsideGroupEncoder encoder = new CompositeInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a().x(1).y(2); + encoder.bCount(1) + .next() + .c().x(3).y(4); + encoder.checkEncodingIsComplete(); + + final CompositeInsideGroupDecoder decoder = new CompositeInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final PointDecoder a = decoder.a(); + assertThat(a.x(), equalTo(1)); + assertThat(a.y(), equalTo(2)); + final CompositeInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + final PointDecoder c = b.next().c(); + assertThat(c.x(), equalTo(3)); + assertThat(c.y(), equalTo(4)); + assertThat(a.x(), equalTo(1)); + assertThat(a.y(), equalTo(2)); + } + + @Test + void allowsReDecodingGroupElementCompositeViaReWrap() + { + final CompositeInsideGroupEncoder encoder = new CompositeInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a().x(1).y(2); + encoder.bCount(1) + .next() + .c().x(3).y(4); + encoder.checkEncodingIsComplete(); + + final CompositeInsideGroupDecoder decoder = new CompositeInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final PointDecoder a = decoder.a(); + assertThat(a.x(), equalTo(1)); + assertThat(a.y(), equalTo(2)); + final CompositeInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + final PointDecoder c1 = b.next().c(); + assertThat(c1.x(), equalTo(3)); + assertThat(c1.y(), equalTo(4)); + final PointDecoder c2 = b.c(); + assertThat(c2.x(), equalTo(3)); + assertThat(c2.y(), equalTo(4)); + } + + @Test + void allowsReDecodingGroupElementCompositeViaEncoderReference() + { + final CompositeInsideGroupEncoder encoder = new CompositeInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a().x(1).y(2); + encoder.bCount(1) + .next() + .c().x(3).y(4); + encoder.checkEncodingIsComplete(); + + final CompositeInsideGroupDecoder decoder = new CompositeInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final PointDecoder a = decoder.a(); + assertThat(a.x(), equalTo(1)); + assertThat(a.y(), equalTo(2)); + final CompositeInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + final PointDecoder c = b.next().c(); + assertThat(c.x(), equalTo(3)); + assertThat(c.y(), equalTo(4)); + assertThat(c.x(), equalTo(3)); + assertThat(c.y(), equalTo(4)); + } + + @Test + void allowsNewDecoderToDecodeAddedPrimitiveField() + { + final AddPrimitiveV1Encoder encoder = new AddPrimitiveV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).b(2); + encoder.checkEncodingIsComplete(); + + final AddPrimitiveV1Decoder decoder = new AddPrimitiveV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.b(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeMissingPrimitiveFieldAsNullValue() + { + final AddPrimitiveV0Encoder encoder = new AddPrimitiveV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddPrimitiveV1Decoder decoder = new AddPrimitiveV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.b(), equalTo(AddPrimitiveV1Decoder.bNullValue())); + } + + @Test + void allowsNewDecoderToDecodeAddedPrimitiveFieldBeforeGroup() + { + final AddPrimitiveBeforeGroupV1Encoder encoder = new AddPrimitiveBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).d(3).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + final AddPrimitiveBeforeGroupV1Decoder decoder = new AddPrimitiveBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.d(), equalTo(3)); + final AddPrimitiveBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeMissingPrimitiveFieldBeforeGroupAsNullValue() + { + final AddPrimitiveBeforeGroupV0Encoder encoder = new AddPrimitiveBeforeGroupV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddPrimitiveBeforeGroupV1Decoder decoder = new AddPrimitiveBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.d(), equalTo(AddPrimitiveBeforeGroupV1Decoder.dNullValue())); + final AddPrimitiveBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToSkipPresentButAddedPrimitiveFieldBeforeGroup() + { + final AddPrimitiveBeforeGroupV1Encoder encoder = new AddPrimitiveBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).d(3).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + final AddPrimitiveBeforeGroupV1Decoder decoder = new AddPrimitiveBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddPrimitiveBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsOldDecoderToSkipAddedPrimitiveFieldBeforeGroup() + { + final AddPrimitiveBeforeGroupV1Encoder encoder = new AddPrimitiveBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).d(3).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion1(); + + final AddPrimitiveBeforeGroupV0Decoder decoder = new AddPrimitiveBeforeGroupV0Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddPrimitiveBeforeGroupV0Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeAddedPrimitiveFieldBeforeVarData() + { + final AddPrimitiveBeforeVarDataV1Encoder encoder = new AddPrimitiveBeforeVarDataV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).c(3).b("abc"); + encoder.checkEncodingIsComplete(); + + final AddPrimitiveBeforeVarDataV1Decoder decoder = new AddPrimitiveBeforeVarDataV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.c(), equalTo(3)); + assertThat(decoder.b(), equalTo("abc")); + } + + @Test + void allowsNewDecoderToDecodeMissingPrimitiveFieldBeforeVarDataAsNullValue() + { + final AddPrimitiveBeforeVarDataV0Encoder encoder = new AddPrimitiveBeforeVarDataV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).b("abc"); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddPrimitiveBeforeVarDataV1Decoder decoder = new AddPrimitiveBeforeVarDataV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.c(), equalTo(AddPrimitiveBeforeVarDataV1Decoder.cNullValue())); + assertThat(decoder.b(), equalTo("abc")); + } + + @Test + void allowsNewDecoderToSkipPresentButAddedPrimitiveFieldBeforeVarData() + { + final AddPrimitiveBeforeVarDataV1Encoder encoder = new AddPrimitiveBeforeVarDataV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).c(3).b("abc"); + encoder.checkEncodingIsComplete(); + + final AddPrimitiveBeforeVarDataV1Decoder decoder = new AddPrimitiveBeforeVarDataV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.b(), equalTo("abc")); + } + + @Test + void allowsOldDecoderToSkipAddedPrimitiveFieldBeforeVarData() + { + final AddPrimitiveBeforeVarDataV1Encoder encoder = new AddPrimitiveBeforeVarDataV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).c(3).b("abc"); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion1(); + + final AddPrimitiveBeforeVarDataV0Decoder decoder = new AddPrimitiveBeforeVarDataV0Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.b(), equalTo("abc")); + } + + @Test + void allowsNewDecoderToDecodeAddedPrimitiveFieldInsideGroup() + { + final AddPrimitiveInsideGroupV1Encoder encoder = new AddPrimitiveInsideGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2).d(3); + encoder.checkEncodingIsComplete(); + + final AddPrimitiveInsideGroupV1Decoder decoder = new AddPrimitiveInsideGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddPrimitiveInsideGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + assertThat(b.d(), equalTo(3)); + } + + @Test + void allowsNewDecoderToDecodeMissingPrimitiveFieldInsideGroupAsNullValue() + { + final AddPrimitiveInsideGroupV0Encoder encoder = new AddPrimitiveInsideGroupV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddPrimitiveInsideGroupV1Decoder decoder = new AddPrimitiveInsideGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddPrimitiveInsideGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + assertThat(b.d(), equalTo(AddPrimitiveInsideGroupV1Decoder.BDecoder.dNullValue())); + } + + @Test + void allowsNewDecoderToSkipPresentButAddedPrimitiveFieldInsideGroup() + { + final AddPrimitiveInsideGroupV1Encoder encoder = new AddPrimitiveInsideGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(2).next().c(2).d(3).next().c(4).d(5); + encoder.checkEncodingIsComplete(); + + final AddPrimitiveInsideGroupV1Decoder decoder = new AddPrimitiveInsideGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddPrimitiveInsideGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(2)); + assertThat(b.next().c(), equalTo(2)); + assertThat(b.next().c(), equalTo(4)); + } + + @Test + void allowsOldDecoderToSkipAddedPrimitiveFieldInsideGroup() + { + final AddPrimitiveInsideGroupV1Encoder encoder = new AddPrimitiveInsideGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(2).next().c(2).d(3).next().c(4).d(5); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion1(); + + final AddPrimitiveInsideGroupV0Decoder decoder = new AddPrimitiveInsideGroupV0Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddPrimitiveInsideGroupV0Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(2)); + assertThat(b.next().c(), equalTo(2)); + assertThat(b.next().c(), equalTo(4)); + } + + @Test + void allowsNewDecoderToDecodeAddedGroupBeforeVarData() + { + final AddGroupBeforeVarDataV1Encoder encoder = new AddGroupBeforeVarDataV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).cCount(1).next().d(2); + encoder.b("abc"); + encoder.checkEncodingIsComplete(); + + final AddGroupBeforeVarDataV1Decoder decoder = new AddGroupBeforeVarDataV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddGroupBeforeVarDataV1Decoder.CDecoder c = decoder.c(); + assertThat(c.count(), equalTo(1)); + assertThat(c.next().d(), equalTo(2)); + assertThat(decoder.b(), equalTo("abc")); + } + + @Test + void allowsNewDecoderToDecodeMissingGroupBeforeVarDataAsNullValue() + { + final AddGroupBeforeVarDataV0Encoder encoder = new AddGroupBeforeVarDataV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).b("abc"); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddGroupBeforeVarDataV1Decoder decoder = new AddGroupBeforeVarDataV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddGroupBeforeVarDataV1Decoder.CDecoder c = decoder.c(); + assertThat(c.count(), equalTo(0)); + assertThat(decoder.b(), equalTo("abc")); + assertThat(decoder.toString(), containsString("a=1|c=[]|b='abc'")); + } + + @Test + void allowsNewDecoderToSkipMissingGroupBeforeVarData() + { + final AddGroupBeforeVarDataV0Encoder encoder = new AddGroupBeforeVarDataV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).b("abc"); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddGroupBeforeVarDataV1Decoder decoder = new AddGroupBeforeVarDataV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.b(), equalTo("abc")); + } + + @Test + void disallowsNewDecoderToSkipPresentButAddedGroupBeforeVarData() + { + final AddGroupBeforeVarDataV1Encoder encoder = new AddGroupBeforeVarDataV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).cCount(1).next().d(2); + encoder.b("abc"); + encoder.checkEncodingIsComplete(); + + final AddGroupBeforeVarDataV1Decoder decoder = new AddGroupBeforeVarDataV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, decoder::b); + assertThat(exception.getMessage(), containsString("Cannot access field \"b\" in state: V1_BLOCK")); + } + + @Test + void allowsOldDecoderToSkipAddedGroupBeforeVarData() + { + final AddGroupBeforeVarDataV1Encoder encoder = new AddGroupBeforeVarDataV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + messageHeaderEncoder.numGroups(1); + encoder.a(1).cCount(1).next().d(2); + encoder.b("abc"); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion1(); + + final AddGroupBeforeVarDataV0Decoder decoder = new AddGroupBeforeVarDataV0Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + + for (int i = 0; i < messageHeaderDecoder.numGroups(); i++) + { + skipGroup(decoder); + } + + assertThat(decoder.b(), equalTo("abc")); + } + + private void skipGroup(final AddGroupBeforeVarDataV0Decoder decoder) + { + final GroupSizeEncodingDecoder groupSizeEncodingDecoder = new GroupSizeEncodingDecoder() + .wrap(buffer, decoder.limit()); + final int bytesToSkip = groupSizeEncodingDecoder.encodedLength() + + groupSizeEncodingDecoder.blockLength() * groupSizeEncodingDecoder.numInGroup(); + decoder.limit(decoder.limit() + bytesToSkip); + } + + @Test + void allowsNewDecoderToDecodeAddedEnumFieldBeforeGroup() + { + final AddEnumBeforeGroupV1Encoder encoder = new AddEnumBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).d(Direction.BUY).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + final AddEnumBeforeGroupV1Decoder decoder = new AddEnumBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.d(), equalTo(Direction.BUY)); + final AddEnumBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeMissingEnumFieldBeforeGroupAsNullValue() + { + final AddEnumBeforeGroupV0Encoder encoder = new AddEnumBeforeGroupV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddEnumBeforeGroupV1Decoder decoder = new AddEnumBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.d(), equalTo(Direction.NULL_VAL)); + final AddEnumBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToSkipPresentButAddedEnumFieldBeforeGroup() + { + final AddEnumBeforeGroupV1Encoder encoder = new AddEnumBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).d(Direction.SELL).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + final AddEnumBeforeGroupV1Decoder decoder = new AddEnumBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddEnumBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsOldDecoderToSkipAddedEnumFieldBeforeGroup() + { + final AddEnumBeforeGroupV1Encoder encoder = new AddEnumBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).d(Direction.BUY).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion1(); + + final AddEnumBeforeGroupV0Decoder decoder = new AddEnumBeforeGroupV0Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddEnumBeforeGroupV0Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeAddedCompositeFieldBeforeGroup() + { + final AddCompositeBeforeGroupV1Encoder encoder = new AddCompositeBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).d().x(-1).y(-2); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + final AddCompositeBeforeGroupV1Decoder decoder = new AddCompositeBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final PointDecoder d = decoder.d(); + assertThat(d, notNullValue()); + assertThat(d.x(), equalTo(-1)); + assertThat(d.y(), equalTo(-2)); + final AddCompositeBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeMissingCompositeFieldBeforeGroupAsNullValue() + { + final AddCompositeBeforeGroupV0Encoder encoder = new AddCompositeBeforeGroupV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddCompositeBeforeGroupV1Decoder decoder = new AddCompositeBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.d(), nullValue()); + final AddCompositeBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToSkipPresentButAddedCompositeFieldBeforeGroup() + { + final AddCompositeBeforeGroupV1Encoder encoder = new AddCompositeBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).d().x(-1).y(-2); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + final AddCompositeBeforeGroupV1Decoder decoder = new AddCompositeBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddCompositeBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsOldDecoderToSkipAddedCompositeFieldBeforeGroup() + { + final AddCompositeBeforeGroupV1Encoder encoder = new AddCompositeBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).d().x(-1).y(-2); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion1(); + + final AddCompositeBeforeGroupV0Decoder decoder = new AddCompositeBeforeGroupV0Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddCompositeBeforeGroupV0Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeAddedArrayFieldBeforeGroup() + { + final AddArrayBeforeGroupV1Encoder encoder = new AddArrayBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1) + .putD((short)1, (short)2, (short)3, (short)4) + .bCount(1) + .next().c(2); + encoder.checkEncodingIsComplete(); + + final AddArrayBeforeGroupV1Decoder decoder = new AddArrayBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.d(0), equalTo((short)1)); + assertThat(decoder.d(1), equalTo((short)2)); + assertThat(decoder.d(2), equalTo((short)3)); + assertThat(decoder.d(3), equalTo((short)4)); + final AddArrayBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeMissingArrayFieldBeforeGroupAsNullValue1() + { + final AddArrayBeforeGroupV0Encoder encoder = new AddArrayBeforeGroupV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddArrayBeforeGroupV1Decoder decoder = new AddArrayBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.d(0), equalTo(AddArrayBeforeGroupV1Decoder.dNullValue())); + assertThat(decoder.d(1), equalTo(AddArrayBeforeGroupV1Decoder.dNullValue())); + assertThat(decoder.d(2), equalTo(AddArrayBeforeGroupV1Decoder.dNullValue())); + assertThat(decoder.d(3), equalTo(AddArrayBeforeGroupV1Decoder.dNullValue())); + final AddArrayBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeMissingArrayFieldBeforeGroupAsNullValue2() + { + final AddArrayBeforeGroupV0Encoder encoder = new AddArrayBeforeGroupV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddArrayBeforeGroupV1Decoder decoder = new AddArrayBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.getD(new byte[8], 0, 8), equalTo(0)); + final AddArrayBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeMissingArrayFieldBeforeGroupAsNullValue3() + { + final AddArrayBeforeGroupV0Encoder encoder = new AddArrayBeforeGroupV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddArrayBeforeGroupV1Decoder decoder = new AddArrayBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.getD(new ExpandableArrayBuffer(), 0, 8), equalTo(0)); + final AddArrayBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeMissingArrayFieldBeforeGroupAsNullValue4() + { + final AddArrayBeforeGroupV0Encoder encoder = new AddArrayBeforeGroupV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddArrayBeforeGroupV1Decoder decoder = new AddArrayBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final UnsafeBuffer buffer = new UnsafeBuffer(); + decoder.wrapD(buffer); + assertThat(buffer.capacity(), equalTo(0)); + final AddArrayBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToSkipPresentButAddedArrayFieldBeforeGroup() + { + final AddArrayBeforeGroupV1Encoder encoder = new AddArrayBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1) + .putD((short)1, (short)2, (short)3, (short)4) + .bCount(1) + .next().c(2); + encoder.checkEncodingIsComplete(); + + final AddArrayBeforeGroupV1Decoder decoder = new AddArrayBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddArrayBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsOldDecoderToSkipAddedArrayFieldBeforeGroup() + { + final AddArrayBeforeGroupV1Encoder encoder = new AddArrayBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1) + .putD((short)1, (short)2, (short)3, (short)4) + .bCount(1) + .next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion1(); + + final AddArrayBeforeGroupV0Decoder decoder = new AddArrayBeforeGroupV0Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddArrayBeforeGroupV0Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeAddedBitSetFieldBeforeGroup() + { + final AddBitSetBeforeGroupV1Encoder encoder = new AddBitSetBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).d().guacamole(true).cheese(true).sourCream(false); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + final AddBitSetBeforeGroupV1Decoder decoder = new AddBitSetBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final FlagsDecoder d = decoder.d(); + assertThat(d, notNullValue()); + assertThat(d.guacamole(), equalTo(true)); + assertThat(d.cheese(), equalTo(true)); + assertThat(d.sourCream(), equalTo(false)); + final AddBitSetBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeMissingBitSetFieldBeforeGroupAsNullValue() + { + final AddBitSetBeforeGroupV0Encoder encoder = new AddBitSetBeforeGroupV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddBitSetBeforeGroupV1Decoder decoder = new AddBitSetBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.d(), nullValue()); + final AddBitSetBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToSkipPresentButAddedBitSetFieldBeforeGroup() + { + final AddBitSetBeforeGroupV1Encoder encoder = new AddBitSetBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).d().guacamole(true).cheese(true).sourCream(false); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + final AddBitSetBeforeGroupV1Decoder decoder = new AddBitSetBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddBitSetBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsOldDecoderToSkipAddedBitSetFieldBeforeGroup() + { + final AddBitSetBeforeGroupV1Encoder encoder = new AddBitSetBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).d().guacamole(true).cheese(true).sourCream(false); + encoder.bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion1(); + + final AddBitSetBeforeGroupV0Decoder decoder = new AddBitSetBeforeGroupV0Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddBitSetBeforeGroupV0Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsEncodingAndDecodingEnumInsideGroupInSchemaDefinedOrder() + { + final EnumInsideGroupEncoder encoder = new EnumInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(Direction.BUY) + .bCount(1) + .next() + .c(Direction.SELL); + encoder.checkEncodingIsComplete(); + + final EnumInsideGroupDecoder decoder = new EnumInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(Direction.BUY)); + final EnumInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(Direction.SELL)); + assertThat(decoder.toString(), containsString("a=BUY|b=[(c=SELL)]")); + } + + @Test + void disallowsEncodingEnumInsideGroupBeforeCallingNext() + { + final EnumInsideGroupEncoder encoder = new EnumInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(Direction.BUY); + final EnumInsideGroupEncoder.BEncoder bEncoder = encoder.bCount(1); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> bEncoder.c(Direction.SELL)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsDecodingEnumInsideGroupBeforeCallingNext() + { + final EnumInsideGroupEncoder encoder = new EnumInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(Direction.BUY) + .bCount(1) + .next() + .c(Direction.SELL); + encoder.checkEncodingIsComplete(); + + final EnumInsideGroupDecoder decoder = new EnumInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(Direction.BUY)); + final EnumInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, b::c); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void allowsReEncodingTopLevelEnum() + { + final EnumInsideGroupEncoder encoder = new EnumInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(Direction.BUY) + .bCount(1) + .next() + .c(Direction.SELL); + + encoder.a(Direction.SELL); + encoder.checkEncodingIsComplete(); + + + final EnumInsideGroupDecoder decoder = new EnumInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(Direction.SELL)); + final EnumInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(Direction.SELL)); + } + + @Test + void allowsEncodingAndDecodingBitSetInsideGroupInSchemaDefinedOrder() + { + final BitSetInsideGroupEncoder encoder = new BitSetInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a().cheese(true).guacamole(true).sourCream(false); + encoder.bCount(1) + .next() + .c().cheese(false).guacamole(false).sourCream(true); + encoder.checkEncodingIsComplete(); + + final BitSetInsideGroupDecoder decoder = new BitSetInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final FlagsDecoder a = decoder.a(); + assertThat(a.guacamole(), equalTo(true)); + assertThat(a.cheese(), equalTo(true)); + assertThat(a.sourCream(), equalTo(false)); + final BitSetInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + final FlagsDecoder c = b.next().c(); + assertThat(c.guacamole(), equalTo(false)); + assertThat(c.cheese(), equalTo(false)); + assertThat(c.sourCream(), equalTo(true)); + assertThat(decoder.toString(), containsString("a={guacamole,cheese}|b=[(c={sourCream})]")); + } + + @Test + void disallowsEncodingBitSetInsideGroupBeforeCallingNext() + { + final BitSetInsideGroupEncoder encoder = new BitSetInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a().cheese(true).guacamole(true).sourCream(false); + final BitSetInsideGroupEncoder.BEncoder bEncoder = encoder.bCount(1); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, bEncoder::c); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsDecodingBitSetInsideGroupBeforeCallingNext() + { + final BitSetInsideGroupEncoder encoder = new BitSetInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a().cheese(true).guacamole(true).sourCream(false); + encoder.bCount(1) + .next() + .c().guacamole(false).cheese(false).sourCream(true); + encoder.checkEncodingIsComplete(); + + final BitSetInsideGroupDecoder decoder = new BitSetInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final FlagsDecoder a = decoder.a(); + assertThat(a.guacamole(), equalTo(true)); + assertThat(a.cheese(), equalTo(true)); + assertThat(a.sourCream(), equalTo(false)); + final BitSetInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, b::c); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void allowsReEncodingTopLevelBitSetViaReWrap() + { + final BitSetInsideGroupEncoder encoder = new BitSetInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a().cheese(true).guacamole(true).sourCream(false); + encoder.bCount(1) + .next() + .c().cheese(false).guacamole(false).sourCream(true); + + encoder.a().sourCream(true); + encoder.checkEncodingIsComplete(); + + final BitSetInsideGroupDecoder decoder = new BitSetInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final FlagsDecoder a = decoder.a(); + assertThat(a.guacamole(), equalTo(true)); + assertThat(a.cheese(), equalTo(true)); + assertThat(a.sourCream(), equalTo(true)); + final BitSetInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + final FlagsDecoder c = b.next().c(); + assertThat(c.guacamole(), equalTo(false)); + assertThat(c.cheese(), equalTo(false)); + assertThat(c.sourCream(), equalTo(true)); + } + + @Test + void allowsEncodingAndDecodingArrayInsideGroupInSchemaDefinedOrder() + { + final ArrayInsideGroupEncoder encoder = new ArrayInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.putA((short)1, (short)2, (short)3, (short)4); + encoder.bCount(1) + .next() + .putC((short)5, (short)6, (short)7, (short)8); + encoder.checkEncodingIsComplete(); + + final ArrayInsideGroupDecoder decoder = new ArrayInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(0), equalTo((short)1)); + assertThat(decoder.a(1), equalTo((short)2)); + assertThat(decoder.a(2), equalTo((short)3)); + assertThat(decoder.a(3), equalTo((short)4)); + final ArrayInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + b.next(); + assertThat(b.c(0), equalTo((short)5)); + assertThat(b.c(1), equalTo((short)6)); + assertThat(b.c(2), equalTo((short)7)); + assertThat(b.c(3), equalTo((short)8)); + assertThat(decoder.toString(), containsString("a=[1,2,3,4]|b=[(c=[5,6,7,8])]")); + } + + @Test + void disallowsEncodingArrayInsideGroupBeforeCallingNext1() + { + final ArrayInsideGroupEncoder.BEncoder bEncoder = encodeUntilGroupWithArrayInside(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> bEncoder.putC((short)5, (short)6, (short)7, (short)8)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsEncodingArrayInsideGroupBeforeCallingNext2() + { + final ArrayInsideGroupEncoder.BEncoder bEncoder = encodeUntilGroupWithArrayInside(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> bEncoder.c(0, (short)5)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsEncodingArrayInsideGroupBeforeCallingNext3() + { + final ArrayInsideGroupEncoder.BEncoder bEncoder = encodeUntilGroupWithArrayInside(); + final byte[] bytes = {5, 6, 7, 8}; + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> bEncoder.putC(bytes, 0, 4)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsEncodingArrayInsideGroupBeforeCallingNext4() + { + final ArrayInsideGroupEncoder.BEncoder bEncoder = encodeUntilGroupWithArrayInside(); + final UnsafeBuffer buffer = new UnsafeBuffer(new byte[8]); + buffer.putByte(0, (byte)5); + buffer.putByte(2, (byte)6); + buffer.putByte(4, (byte)7); + buffer.putByte(6, (byte)8); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> bEncoder.putC(buffer, 0, 4)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + private ArrayInsideGroupEncoder.BEncoder encodeUntilGroupWithArrayInside() + { + final ArrayInsideGroupEncoder encoder = new ArrayInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.putA((short)1, (short)2, (short)3, (short)4); + return encoder.bCount(1); + } + + @Test + void disallowsDecodingArrayInsideGroupBeforeCallingNext1() + { + final ArrayInsideGroupDecoder.BDecoder b = decodeUntilGroupWithArrayInside(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> b.c(0)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsDecodingArrayInsideGroupBeforeCallingNext2() + { + final ArrayInsideGroupDecoder.BDecoder b = decodeUntilGroupWithArrayInside(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> b.getC(new byte[8], 0, 8)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsDecodingArrayInsideGroupBeforeCallingNext3() + { + final ArrayInsideGroupDecoder.BDecoder b = decodeUntilGroupWithArrayInside(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> b.getC(new ExpandableArrayBuffer(), 0, 8)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsDecodingArrayInsideGroupBeforeCallingNext4() + { + final ArrayInsideGroupDecoder.BDecoder b = decodeUntilGroupWithArrayInside(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> b.wrapC(new UnsafeBuffer())); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + private ArrayInsideGroupDecoder.BDecoder decodeUntilGroupWithArrayInside() + { + final ArrayInsideGroupEncoder encoder = new ArrayInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.putA((short)1, (short)2, (short)3, (short)4); + encoder.bCount(1) + .next() + .putC((short)5, (short)6, (short)7, (short)8); + encoder.checkEncodingIsComplete(); + + final ArrayInsideGroupDecoder decoder = new ArrayInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(0), equalTo((short)1)); + assertThat(decoder.a(1), equalTo((short)2)); + assertThat(decoder.a(2), equalTo((short)3)); + assertThat(decoder.a(3), equalTo((short)4)); + final ArrayInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + return b; + } + + @Test + void allowsReEncodingTopLevelArrayViaReWrap() + { + final ArrayInsideGroupEncoder encoder = new ArrayInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.putA((short)1, (short)2, (short)3, (short)4); + encoder.bCount(1) + .next() + .putC((short)5, (short)6, (short)7, (short)8); + + encoder.putA((short)9, (short)10, (short)11, (short)12); + encoder.checkEncodingIsComplete(); + + final ArrayInsideGroupDecoder decoder = new ArrayInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(0), equalTo((short)9)); + assertThat(decoder.a(1), equalTo((short)10)); + assertThat(decoder.a(2), equalTo((short)11)); + assertThat(decoder.a(3), equalTo((short)12)); + final ArrayInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + b.next(); + assertThat(b.c(0), equalTo((short)5)); + assertThat(b.c(1), equalTo((short)6)); + assertThat(b.c(2), equalTo((short)7)); + assertThat(b.c(3), equalTo((short)8)); + } + + @Test + void allowsEncodingAndDecodingGroupFieldsInSchemaDefinedOrder1() + { + final MultipleGroupsEncoder encoder = new MultipleGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.bCount(0); + encoder.dCount(1).next().e(43); + encoder.checkEncodingIsComplete(); + + final MultipleGroupsDecoder decoder = new MultipleGroupsDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + assertThat(decoder.b().count(), equalTo(0)); + final MultipleGroupsDecoder.DDecoder d = decoder.d(); + assertThat(d.count(), equalTo(1)); + assertThat(d.next().e(), equalTo(43)); + assertThat(decoder.toString(), containsString("a=42|b=[]|d=[(e=43)]")); + } + + @Test + void allowsEncodingAndDecodingGroupFieldsInSchemaDefinedOrder2() + { + final MultipleGroupsEncoder encoder = new MultipleGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(41); + encoder.bCount(1).next().c(42); + encoder.dCount(1).next().e(43); + encoder.checkEncodingIsComplete(); + + final MultipleGroupsDecoder decoder = new MultipleGroupsDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(41)); + final MultipleGroupsDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(42)); + final MultipleGroupsDecoder.DDecoder d = decoder.d(); + assertThat(d.count(), equalTo(1)); + assertThat(d.next().e(), equalTo(43)); + } + + @Test + void allowsReEncodingTopLevelPrimitiveFieldsAfterGroups() + { + final MultipleGroupsEncoder encoder = new MultipleGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(41); + encoder.bCount(1).next().c(42); + encoder.dCount(1).next().e(43); + encoder.a(44); + encoder.checkEncodingIsComplete(); + + final MultipleGroupsDecoder decoder = new MultipleGroupsDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(44)); + final MultipleGroupsDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(42)); + final MultipleGroupsDecoder.DDecoder d = decoder.d(); + assertThat(d.count(), equalTo(1)); + assertThat(d.next().e(), equalTo(43)); + } + + @Test + void disallowsMissedEncodingOfGroupField() + { + final MultipleGroupsEncoder encoder = new MultipleGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(41); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> encoder.dCount(0)); + assertThat(exception.getMessage(), + containsString("Cannot encode count of repeating group \"d\" in state: V0_BLOCK")); + } + + @Test + void disallowsReEncodingEarlierGroupFields() + { + final MultipleGroupsEncoder encoder = new MultipleGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(41); + encoder.bCount(1).next().c(42); + encoder.dCount(1).next().e(43); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> encoder.bCount(1)); + assertThat(exception.getMessage(), + containsString("Cannot encode count of repeating group \"b\" in state: V0_D_1_BLOCK")); + } + + @Test + void disallowsReEncodingLatestGroupField() + { + final MultipleGroupsEncoder encoder = new MultipleGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(41); + encoder.bCount(1).next().c(42); + encoder.dCount(1).next().e(43); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> encoder.dCount(1)); + assertThat(exception.getMessage(), + containsString("Cannot encode count of repeating group \"d\" in state: V0_D_1_BLOCK")); + } + + @Test + void disallowsMissedDecodingOfGroupField() + { + final MultipleGroupsEncoder encoder = new MultipleGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(41); + encoder.bCount(1).next().c(42); + encoder.dCount(1).next().e(43); + encoder.checkEncodingIsComplete(); + + final MultipleGroupsDecoder decoder = new MultipleGroupsDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(41)); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, decoder::d); + assertThat(exception.getMessage(), + containsString("Cannot decode count of repeating group \"d\" in state: V0_BLOCK")); + } + + @Test + void disallowsReDecodingEarlierGroupField() + { + final MultipleGroupsEncoder encoder = new MultipleGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(41); + encoder.bCount(1).next().c(42); + encoder.dCount(1).next().e(43); + encoder.checkEncodingIsComplete(); + + final MultipleGroupsDecoder decoder = new MultipleGroupsDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(41)); + final MultipleGroupsDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(42)); + final MultipleGroupsDecoder.DDecoder d = decoder.d(); + assertThat(d.count(), equalTo(1)); + assertThat(d.next().e(), equalTo(43)); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, decoder::b); + assertThat(exception.getMessage(), + containsString("Cannot decode count of repeating group \"b\" in state: V0_D_1_BLOCK")); + } + + @Test + void disallowsReDecodingLatestGroupField() + { + final MultipleGroupsEncoder encoder = new MultipleGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(41); + encoder.bCount(1).next().c(42); + encoder.dCount(1).next().e(43); + encoder.checkEncodingIsComplete(); + + final MultipleGroupsDecoder decoder = new MultipleGroupsDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(41)); + final MultipleGroupsDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(42)); + final MultipleGroupsDecoder.DDecoder d = decoder.d(); + assertThat(d.count(), equalTo(1)); + assertThat(d.next().e(), equalTo(43)); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, decoder::d); + assertThat(exception.getMessage(), + containsString("Cannot decode count of repeating group \"d\" in state: V0_D_1_BLOCK")); + } + + @Test + void allowsNewDecoderToDecodeAddedVarData() + { + final AddVarDataV1Encoder encoder = new AddVarDataV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.b("abc"); + encoder.checkEncodingIsComplete(); + + final AddVarDataV1Decoder decoder = new AddVarDataV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + assertThat(decoder.b(), equalTo("abc")); + } + + @Test + void allowsNewDecoderToDecodeMissingAddedVarDataAsNullValue1() + { + final AddVarDataV0Encoder encoder = new AddVarDataV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddVarDataV1Decoder decoder = new AddVarDataV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + assertThat(decoder.b(), equalTo("")); + assertThat(decoder.toString(), containsString("a=42|b=''")); + } + + @Test + void allowsNewDecoderToDecodeMissingAddedVarDataAsNullValue2() + { + final AddVarDataV0Encoder encoder = new AddVarDataV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddVarDataV1Decoder decoder = new AddVarDataV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + assertThat(decoder.getB(new StringBuilder()), equalTo(0)); + } + + @Test + void allowsNewDecoderToDecodeMissingAddedVarDataAsNullValue3() + { + final AddVarDataV0Encoder encoder = new AddVarDataV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddVarDataV1Decoder decoder = new AddVarDataV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + assertThat(decoder.getB(new byte[3], 0, 3), equalTo(0)); + } + + @Test + void allowsNewDecoderToDecodeMissingAddedVarDataAsNullValue4() + { + final AddVarDataV0Encoder encoder = new AddVarDataV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddVarDataV1Decoder decoder = new AddVarDataV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + assertThat(decoder.getB(new ExpandableArrayBuffer(), 0, 3), equalTo(0)); + } + + @Test + void allowsNewDecoderToDecodeMissingAddedVarDataAsNullValue5() + { + final AddVarDataV0Encoder encoder = new AddVarDataV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddVarDataV1Decoder decoder = new AddVarDataV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(42)); + assertThat(decoder.bLength(), equalTo(0)); + } + + @Test + void allowsEncodingAndDecodingAsciiInsideGroupInSchemaDefinedOrder1() + { + final AsciiInsideGroupEncoder encoder = new AsciiInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a("GBPUSD"); + encoder.bCount(1) + .next() + .c("EURUSD"); + encoder.checkEncodingIsComplete(); + + final AsciiInsideGroupDecoder decoder = new AsciiInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo("GBPUSD")); + final AsciiInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + b.next(); + assertThat(b.c(), equalTo("EURUSD")); + assertThat(decoder.toString(), containsString("a=GBPUSD|b=[(c=EURUSD)]")); + } + + @Test + void allowsEncodingAndDecodingAsciiInsideGroupInSchemaDefinedOrder2() + { + final AsciiInsideGroupEncoder encoder = new AsciiInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + final byte[] gbpUsdBytes = "GBPUSD".getBytes(StandardCharsets.US_ASCII); + encoder.putA(gbpUsdBytes, 0); + encoder.bCount(1) + .next() + .c(0, (byte)'E') + .c(1, (byte)'U') + .c(2, (byte)'R') + .c(3, (byte)'U') + .c(4, (byte)'S') + .c(5, (byte)'D'); + encoder.checkEncodingIsComplete(); + + final AsciiInsideGroupDecoder decoder = new AsciiInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final byte[] aBytes = new byte[6]; + decoder.getA(aBytes, 0); + assertThat(aBytes, equalTo(gbpUsdBytes)); + final AsciiInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + b.next(); + assertThat(b.c(0), equalTo((byte)'E')); + assertThat(b.c(1), equalTo((byte)'U')); + assertThat(b.c(2), equalTo((byte)'R')); + assertThat(b.c(3), equalTo((byte)'U')); + assertThat(b.c(4), equalTo((byte)'S')); + assertThat(b.c(5), equalTo((byte)'D')); + } + + @Test + void disallowsEncodingAsciiInsideGroupBeforeCallingNext1() + { + final AsciiInsideGroupEncoder.BEncoder bEncoder = encodeUntilGroupWithAsciiInside(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> bEncoder.c("EURUSD")); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsEncodingAsciiInsideGroupBeforeCallingNext2() + { + final AsciiInsideGroupEncoder.BEncoder bEncoder = encodeUntilGroupWithAsciiInside(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> bEncoder.c(0, (byte)'E')); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsEncodingAsciiInsideGroupBeforeCallingNext3() + { + final AsciiInsideGroupEncoder.BEncoder bEncoder = encodeUntilGroupWithAsciiInside(); + final byte[] eurUsdBytes = "EURUSD".getBytes(StandardCharsets.US_ASCII); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> bEncoder.putC(eurUsdBytes, 0)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + private AsciiInsideGroupEncoder.BEncoder encodeUntilGroupWithAsciiInside() + { + final AsciiInsideGroupEncoder encoder = new AsciiInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a("GBPUSD"); + return encoder.bCount(1); + } + + @Test + void disallowsDecodingAsciiInsideGroupBeforeCallingNext1() + { + final AsciiInsideGroupDecoder.BDecoder b = decodeUntilGroupWithAsciiInside(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> b.c(0)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsDecodingAsciiInsideGroupBeforeCallingNext2() + { + final AsciiInsideGroupDecoder.BDecoder b = decodeUntilGroupWithAsciiInside(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> b.getC(new byte[6], 0)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsDecodingAsciiInsideGroupBeforeCallingNext3() + { + final AsciiInsideGroupDecoder.BDecoder b = decodeUntilGroupWithAsciiInside(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, b::c); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + @Test + void disallowsDecodingAsciiInsideGroupBeforeCallingNext4() + { + final AsciiInsideGroupDecoder.BDecoder b = decodeUntilGroupWithAsciiInside(); + final Exception exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> b.getC(new StringBuilder())); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_B_N")); + } + + private AsciiInsideGroupDecoder.BDecoder decodeUntilGroupWithAsciiInside() + { + final AsciiInsideGroupEncoder encoder = new AsciiInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a("GBPUSD"); + encoder.bCount(1) + .next() + .c("EURUSD"); + encoder.checkEncodingIsComplete(); + + final AsciiInsideGroupDecoder decoder = new AsciiInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo("GBPUSD")); + final AsciiInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + return b; + } + + @Test + void allowsReEncodingTopLevelAsciiViaReWrap() + { + final AsciiInsideGroupEncoder encoder = new AsciiInsideGroupEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a("GBPUSD"); + encoder.bCount(1) + .next() + .c("EURUSD"); + + encoder.a("CADUSD"); + encoder.checkEncodingIsComplete(); + + final AsciiInsideGroupDecoder decoder = new AsciiInsideGroupDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo("CADUSD")); + final AsciiInsideGroupDecoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + b.next(); + assertThat(b.c(), equalTo("EURUSD")); + } + + @Test + void allowsNewDecoderToDecodeAddedAsciiFieldBeforeGroup1() + { + final AddAsciiBeforeGroupV1Encoder encoder = new AddAsciiBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1) + .d("EURUSD") + .bCount(1) + .next().c(2); + encoder.checkEncodingIsComplete(); + + final AddAsciiBeforeGroupV1Decoder decoder = new AddAsciiBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.d(), equalTo("EURUSD")); + final AddAsciiBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeAddedAsciiFieldBeforeGroup2() + { + final AddAsciiBeforeGroupV1Encoder encoder = new AddAsciiBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1) + .d("EURUSD") + .bCount(1) + .next().c(2); + encoder.checkEncodingIsComplete(); + + final AddAsciiBeforeGroupV1Decoder decoder = new AddAsciiBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final StringBuilder aValue = new StringBuilder(); + decoder.getD(aValue); + assertThat(aValue.toString(), equalTo("EURUSD")); + final AddAsciiBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + + @Test + void allowsNewDecoderToDecodeAddedAsciiFieldBeforeGroup3() + { + final AddAsciiBeforeGroupV1Encoder encoder = new AddAsciiBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + final byte[] eurUsdBytes = "EURUSD".getBytes(StandardCharsets.US_ASCII); + encoder.a(1) + .putD(eurUsdBytes, 0) + .bCount(1) + .next().c(2); + encoder.checkEncodingIsComplete(); + + final AddAsciiBeforeGroupV1Decoder decoder = new AddAsciiBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final byte[] aBytes = new byte[6]; + decoder.getD(aBytes, 0); + assertThat(aBytes, equalTo(eurUsdBytes)); + final AddAsciiBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + + @Test + void allowsNewDecoderToDecodeAddedAsciiFieldBeforeGroup4() + { + final AddAsciiBeforeGroupV1Encoder encoder = new AddAsciiBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1) + .d(0, (byte)'E') + .d(1, (byte)'U') + .d(2, (byte)'R') + .d(3, (byte)'U') + .d(4, (byte)'S') + .d(5, (byte)'D') + .bCount(1) + .next().c(2); + encoder.checkEncodingIsComplete(); + + final AddAsciiBeforeGroupV1Decoder decoder = new AddAsciiBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.d(0), equalTo((byte)'E')); + assertThat(decoder.d(1), equalTo((byte)'U')); + assertThat(decoder.d(2), equalTo((byte)'R')); + assertThat(decoder.d(3), equalTo((byte)'U')); + assertThat(decoder.d(4), equalTo((byte)'S')); + assertThat(decoder.d(5), equalTo((byte)'D')); + final AddAsciiBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeMissingAsciiFieldBeforeGroupAsNullValue1() + { + final AddAsciiBeforeGroupV0Encoder encoder = new AddAsciiBeforeGroupV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddAsciiBeforeGroupV1Decoder decoder = new AddAsciiBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.d(0), equalTo(AddAsciiBeforeGroupV1Decoder.dNullValue())); + assertThat(decoder.d(1), equalTo(AddAsciiBeforeGroupV1Decoder.dNullValue())); + assertThat(decoder.d(2), equalTo(AddAsciiBeforeGroupV1Decoder.dNullValue())); + assertThat(decoder.d(3), equalTo(AddAsciiBeforeGroupV1Decoder.dNullValue())); + assertThat(decoder.d(4), equalTo(AddAsciiBeforeGroupV1Decoder.dNullValue())); + assertThat(decoder.d(5), equalTo(AddAsciiBeforeGroupV1Decoder.dNullValue())); + final AddAsciiBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeMissingAsciiFieldBeforeGroupAsNullValue2() + { + final AddAsciiBeforeGroupV0Encoder encoder = new AddAsciiBeforeGroupV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddAsciiBeforeGroupV1Decoder decoder = new AddAsciiBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.d(), equalTo("")); + final AddAsciiBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeMissingAsciiFieldBeforeGroupAsNullValue3() + { + final AddAsciiBeforeGroupV0Encoder encoder = new AddAsciiBeforeGroupV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddAsciiBeforeGroupV1Decoder decoder = new AddAsciiBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final StringBuilder aValue = new StringBuilder(); + assertThat(decoder.getD(aValue), equalTo(0)); + assertThat(aValue.length(), equalTo(0)); + final AddAsciiBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToDecodeMissingAsciiFieldBeforeGroupAsNullValue4() + { + final AddAsciiBeforeGroupV0Encoder encoder = new AddAsciiBeforeGroupV0Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion0(); + + final AddAsciiBeforeGroupV1Decoder decoder = new AddAsciiBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + assertThat(decoder.getD(new byte[6], 0), equalTo(0)); + final AddAsciiBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsNewDecoderToSkipPresentButAddedAsciiFieldBeforeGroup() + { + final AddAsciiBeforeGroupV1Encoder encoder = new AddAsciiBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1) + .d("EURUSD") + .bCount(1) + .next().c(2); + encoder.checkEncodingIsComplete(); + + final AddAsciiBeforeGroupV1Decoder decoder = new AddAsciiBeforeGroupV1Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddAsciiBeforeGroupV1Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsOldDecoderToSkipAddedAsciiFieldBeforeGroup() + { + final AddAsciiBeforeGroupV1Encoder encoder = new AddAsciiBeforeGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1) + .d("EURUSD") + .bCount(1) + .next().c(2); + encoder.checkEncodingIsComplete(); + + modifyHeaderToLookLikeVersion1(); + + final AddAsciiBeforeGroupV0Decoder decoder = new AddAsciiBeforeGroupV0Decoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final AddAsciiBeforeGroupV0Decoder.BDecoder b = decoder.b(); + assertThat(b.count(), equalTo(1)); + assertThat(b.next().c(), equalTo(2)); + } + + @Test + void allowsEncodeAndDecodeOfMessagesWithNoBlock() + { + final NoBlockEncoder encoder = new NoBlockEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a("abc"); + encoder.checkEncodingIsComplete(); + + final NoBlockDecoder decoder = new NoBlockDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo("abc")); + } + + @Test + void allowsEncodeAndDecodeOfGroupsWithNoBlock() + { + final GroupWithNoBlockEncoder encoder = new GroupWithNoBlockEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.aCount(1).next().b("abc"); + encoder.checkEncodingIsComplete(); + + final GroupWithNoBlockDecoder decoder = new GroupWithNoBlockDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + final GroupWithNoBlockDecoder.ADecoder a = decoder.a(); + assertThat(a.count(), equalTo(1)); + assertThat(a.next().b(), equalTo("abc")); + } + + @Test + void disallowsEncodingElementOfEmptyGroup1() + { + final MultipleGroupsEncoder encoder = new MultipleGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final MultipleGroupsEncoder.BEncoder bEncoder = encoder.bCount(0); + encoder.dCount(1).next().e(43); + final IllegalStateException exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> bEncoder.c(44)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_D_1_BLOCK")); + } + + @Test + void disallowsEncodingElementOfEmptyGroup2() + { + final NestedGroupsEncoder encoder = new NestedGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final NestedGroupsEncoder.BEncoder bEncoder = encoder.bCount(1); + bEncoder.next(); + bEncoder.c(43); + final NestedGroupsEncoder.BEncoder.DEncoder dEncoder = bEncoder.dCount(0); + bEncoder.fCount(0); + encoder.hCount(0); + final IllegalStateException exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> dEncoder.e(44)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.d.e\" in state: V0_H_DONE")); + } + + @Test + void disallowsEncodingElementOfEmptyGroup3() + { + final NestedGroupsEncoder encoder = new NestedGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final NestedGroupsEncoder.BEncoder bEncoder = encoder.bCount(1); + bEncoder.next(); + bEncoder.c(43); + final NestedGroupsEncoder.BEncoder.DEncoder dEncoder = bEncoder.dCount(0); + bEncoder.fCount(0); + final IllegalStateException exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> dEncoder.e(44)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.d.e\" in state: V0_B_1_F_DONE")); + } + + @Test + void disallowsEncodingElementOfEmptyGroup4() + { + final AddPrimitiveInsideGroupV1Encoder encoder = new AddPrimitiveInsideGroupV1Encoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final AddPrimitiveInsideGroupV1Encoder.BEncoder bEncoder = encoder.bCount(0); + final IllegalStateException exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> bEncoder.c(43)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V1_B_DONE")); + } + + @Test + void disallowsEncodingElementOfEmptyGroup5() + { + final GroupAndVarLengthEncoder encoder = new GroupAndVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(42); + final GroupAndVarLengthEncoder.BEncoder bEncoder = encoder.bCount(0); + encoder.d("abc"); + final IllegalStateException exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, () -> bEncoder.c(43)); + assertThat(exception.getMessage(), containsString("Cannot access field \"b.c\" in state: V0_D_DONE")); + } + + @Test + void allowsEncodingAndDecodingNestedGroupWithVarDataInSchemaDefinedOrder() + { + final NestedGroupWithVarLengthEncoder encoder = new NestedGroupWithVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1); + final NestedGroupWithVarLengthEncoder.BEncoder bEncoder = encoder.bCount(3); + bEncoder.next().c(2).dCount(0); + bEncoder.next().c(3).dCount(1).next().e(4).f("abc"); + bEncoder.next().c(5).dCount(2).next().e(6).f("def").next().e(7).f("ghi"); + encoder.checkEncodingIsComplete(); + + final NestedGroupWithVarLengthDecoder decoder = new NestedGroupWithVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final NestedGroupWithVarLengthDecoder.BDecoder bDecoder = decoder.b(); + assertThat(bDecoder.count(), equalTo(3)); + assertThat(bDecoder.next().c(), equalTo(2)); + assertThat(bDecoder.d().count(), equalTo(0)); + assertThat(bDecoder.next().c(), equalTo(3)); + final NestedGroupWithVarLengthDecoder.BDecoder.DDecoder dDecoder = bDecoder.d(); + assertThat(dDecoder.count(), equalTo(1)); + assertThat(dDecoder.next().e(), equalTo(4)); + assertThat(dDecoder.f(), equalTo("abc")); + assertThat(bDecoder.next().c(), equalTo(5)); + assertThat(bDecoder.d(), sameInstance(dDecoder)); + assertThat(dDecoder.count(), equalTo(2)); + assertThat(dDecoder.next().e(), equalTo(6)); + assertThat(dDecoder.f(), equalTo("def")); + assertThat(dDecoder.next().e(), equalTo(7)); + assertThat(dDecoder.f(), equalTo("ghi")); + } + + @CsvSource(value = { + "1,V0_B_1_D_N_BLOCK", + "2,V0_B_N_D_N_BLOCK" + }) + @ParameterizedTest + void disallowsMissedEncodingOfVarLengthFieldInNestedGroupToNextInnerElement( + final int bCount, + final String expectedState) + { + final NestedGroupWithVarLengthEncoder encoder = new NestedGroupWithVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1); + final NestedGroupWithVarLengthEncoder.BEncoder bEncoder = encoder.bCount(bCount); + final NestedGroupWithVarLengthEncoder.BEncoder.DEncoder dEncoder = bEncoder.next().c(5).dCount(2); + dEncoder.next().e(7); + final IllegalStateException exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, dEncoder::next); + assertThat(exception.getMessage(), + containsString("Cannot access next element in repeating group \"b.d\" in state: " + expectedState)); + assertThat(exception.getMessage(), + containsString("Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + } + + @CsvSource(value = { + "1,V0_B_N_D_1_BLOCK", + "2,V0_B_N_D_N_BLOCK", + }) + @ParameterizedTest + void disallowsMissedEncodingOfVarLengthFieldInNestedGroupToNextOuterElement( + final int dCount, + final String expectedState) + { + final NestedGroupWithVarLengthEncoder encoder = new NestedGroupWithVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1); + final NestedGroupWithVarLengthEncoder.BEncoder bEncoder = encoder.bCount(2); + bEncoder.next().c(3).dCount(dCount).next().e(4); + final IllegalStateException exception = + assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, bEncoder::next); + assertThat(exception.getMessage(), + containsString("Cannot access next element in repeating group \"b\" in state: " + expectedState)); + assertThat(exception.getMessage(), + containsString("Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + } + + @Test + void disallowsMissedDecodingOfVarLengthFieldInNestedGroupToNextInnerElement1() + { + final NestedGroupWithVarLengthEncoder encoder = new NestedGroupWithVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1); + final NestedGroupWithVarLengthEncoder.BEncoder bEncoder = encoder.bCount(1); + bEncoder.next().c(2).dCount(2).next().e(3).f("abc").next().e(4).f("def"); + encoder.checkEncodingIsComplete(); + + final NestedGroupWithVarLengthDecoder decoder = new NestedGroupWithVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final NestedGroupWithVarLengthDecoder.BDecoder bDecoder = decoder.b(); + assertThat(bDecoder.count(), equalTo(1)); + assertThat(bDecoder.next().c(), equalTo(2)); + final NestedGroupWithVarLengthDecoder.BDecoder.DDecoder dDecoder = bDecoder.d(); + assertThat(dDecoder.count(), equalTo(2)); + assertThat(dDecoder.next().e(), equalTo(3)); + final Exception exception = assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, dDecoder::next); + assertThat(exception.getMessage(), + containsString("Cannot access next element in repeating group \"b.d\" in state: V0_B_1_D_N_BLOCK.")); + assertThat(exception.getMessage(), + containsString("Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + } + + @Test + void disallowsMissedDecodingOfVarLengthFieldInNestedGroupToNextInnerElement2() + { + final NestedGroupWithVarLengthEncoder encoder = new NestedGroupWithVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1); + final NestedGroupWithVarLengthEncoder.BEncoder bEncoder = encoder.bCount(2); + bEncoder.next().c(2).dCount(2).next().e(3).f("abc").next().e(4).f("def"); + bEncoder.next().c(5).dCount(0); + encoder.checkEncodingIsComplete(); + + final NestedGroupWithVarLengthDecoder decoder = new NestedGroupWithVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final NestedGroupWithVarLengthDecoder.BDecoder bDecoder = decoder.b(); + assertThat(bDecoder.count(), equalTo(2)); + assertThat(bDecoder.next().c(), equalTo(2)); + final NestedGroupWithVarLengthDecoder.BDecoder.DDecoder dDecoder = bDecoder.d(); + assertThat(dDecoder.count(), equalTo(2)); + assertThat(dDecoder.next().e(), equalTo(3)); + final Exception exception = assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, dDecoder::next); + assertThat(exception.getMessage(), + containsString("Cannot access next element in repeating group \"b.d\" in state: V0_B_N_D_N_BLOCK.")); + assertThat(exception.getMessage(), + containsString("Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + } + + @Test + void disallowsMissedDecodingOfVarLengthFieldInNestedGroupToNextOuterElement1() + { + final NestedGroupWithVarLengthEncoder encoder = new NestedGroupWithVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1); + final NestedGroupWithVarLengthEncoder.BEncoder bEncoder = encoder.bCount(2); + bEncoder.next().c(2).dCount(2).next().e(3).f("abc").next().e(4).f("def"); + bEncoder.next().c(5).dCount(0); + encoder.checkEncodingIsComplete(); + + final NestedGroupWithVarLengthDecoder decoder = new NestedGroupWithVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final NestedGroupWithVarLengthDecoder.BDecoder bDecoder = decoder.b(); + assertThat(bDecoder.count(), equalTo(2)); + assertThat(bDecoder.next().c(), equalTo(2)); + final NestedGroupWithVarLengthDecoder.BDecoder.DDecoder dDecoder = bDecoder.d(); + assertThat(dDecoder.count(), equalTo(2)); + assertThat(dDecoder.next().e(), equalTo(3)); + final Exception exception = assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, bDecoder::next); + assertThat(exception.getMessage(), + containsString("Cannot access next element in repeating group \"b\" in state: V0_B_N_D_N_BLOCK.")); + assertThat(exception.getMessage(), + containsString("Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + } + + @Test + void disallowsMissedDecodingOfVarLengthFieldInNestedGroupToNextOuterElement2() + { + final NestedGroupWithVarLengthEncoder encoder = new NestedGroupWithVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1); + final NestedGroupWithVarLengthEncoder.BEncoder bEncoder = encoder.bCount(2); + bEncoder.next().c(2).dCount(1).next().e(3).f("abc"); + bEncoder.next().c(5).dCount(0); + encoder.checkEncodingIsComplete(); + + final NestedGroupWithVarLengthDecoder decoder = new NestedGroupWithVarLengthDecoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderDecoder); + assertThat(decoder.a(), equalTo(1)); + final NestedGroupWithVarLengthDecoder.BDecoder bDecoder = decoder.b(); + assertThat(bDecoder.count(), equalTo(2)); + assertThat(bDecoder.next().c(), equalTo(2)); + final NestedGroupWithVarLengthDecoder.BDecoder.DDecoder dDecoder = bDecoder.d(); + assertThat(dDecoder.count(), equalTo(1)); + assertThat(dDecoder.next().e(), equalTo(3)); + final Exception exception = assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, bDecoder::next); + assertThat(exception.getMessage(), + containsString("Cannot access next element in repeating group \"b\" in state: V0_B_N_D_1_BLOCK.")); + assertThat(exception.getMessage(), + containsString("Expected one of these transitions: [\"b.d.e(?)\", \"b.d.fLength()\", \"b.d.f(?)\"].")); + } + + @Test + void disallowsIncompleteMessagesDueToMissingVarLengthField1() + { + final MultipleVarLengthEncoder encoder = new MultipleVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).b("abc"); + final Exception exception = assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, encoder::checkEncodingIsComplete); + assertThat(exception.getMessage(), containsString( + "Not fully encoded, current state: V0_B_DONE, allowed transitions: \"cLength()\", \"c(?)\"")); + } + + @Test + void disallowsIncompleteMessagesDueToMissingVarLengthField2() + { + final NoBlockEncoder encoder = new NoBlockEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + final Exception exception = assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, encoder::checkEncodingIsComplete); + assertThat(exception.getMessage(), containsString( + "Not fully encoded, current state: V0_BLOCK, allowed transitions: \"aLength()\", \"a(?)\"")); + } + + @Test + void disallowsIncompleteMessagesDueToMissingTopLevelGroup1() + { + final MultipleGroupsEncoder encoder = new MultipleGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(0); + final Exception exception = assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, encoder::checkEncodingIsComplete); + assertThat(exception.getMessage(), containsString( + "Not fully encoded, current state: V0_B_DONE, allowed transitions: " + + "\"b.resetCountToIndex()\", \"dCount(0)\", \"dCount(>0)\"")); + } + + @Test + void disallowsIncompleteMessagesDueToMissingTopLevelGroup2() + { + final MultipleGroupsEncoder encoder = new MultipleGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(1).next().c(2); + final Exception exception = assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, encoder::checkEncodingIsComplete); + assertThat(exception.getMessage(), containsString( + "Not fully encoded, current state: V0_B_1_BLOCK, allowed transitions: " + + "\"b.c(?)\", \"b.resetCountToIndex()\", \"dCount(0)\", \"dCount(>0)\"")); + } + + @Test + void disallowsIncompleteMessagesDueToMissingTopLevelGroup3() + { + final MultipleGroupsEncoder encoder = new MultipleGroupsEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1); + final Exception exception = assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, encoder::checkEncodingIsComplete); + assertThat(exception.getMessage(), containsString( + "Not fully encoded, current state: V0_BLOCK, allowed transitions: " + + "\"a(?)\", \"bCount(0)\", \"bCount(>0)\"")); + } + + @CsvSource(value = { + "1,V0_B_1_BLOCK", + "2,V0_B_N_BLOCK", + }) + @ParameterizedTest + void disallowsIncompleteMessagesDueToMissingNestedGroup1(final int bCount, final String expectedState) + { + final NestedGroupWithVarLengthEncoder encoder = new NestedGroupWithVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(bCount).next().c(2); + final Exception exception = assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, encoder::checkEncodingIsComplete); + assertThat(exception.getMessage(), containsString("Not fully encoded, current state: " + expectedState)); + } + + @CsvSource(value = { + "1,1,V0_B_1_D_N", + "1,2,V0_B_1_D_N", + "2,0,V0_B_N_D_DONE", + "2,1,V0_B_N_D_N", + "2,2,V0_B_N_D_N", + }) + @ParameterizedTest + void disallowsIncompleteMessagesDueToMissingNestedGroup2( + final int bCount, + final int dCount, + final String expectedState) + { + final NestedGroupWithVarLengthEncoder encoder = new NestedGroupWithVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(bCount).next().c(2).dCount(dCount); + final Exception exception = assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, encoder::checkEncodingIsComplete); + assertThat(exception.getMessage(), containsString("Not fully encoded, current state: " + expectedState)); + } + + @CsvSource(value = { + "1,1,V0_B_1_D_1_BLOCK", + "1,2,V0_B_1_D_N_BLOCK", + "2,1,V0_B_N_D_1_BLOCK", + "2,2,V0_B_N_D_N_BLOCK", + }) + @ParameterizedTest + void disallowsIncompleteMessagesDueToMissingVarDataInNestedGroup( + final int bCount, + final int dCount, + final String expectedState) + { + final NestedGroupWithVarLengthEncoder encoder = new NestedGroupWithVarLengthEncoder() + .wrapAndApplyHeader(buffer, OFFSET, messageHeaderEncoder); + encoder.a(1).bCount(bCount).next().c(2).dCount(dCount).next().e(10); + final Exception exception = assertThrows(INCORRECT_ORDER_EXCEPTION_CLASS, encoder::checkEncodingIsComplete); + assertThat(exception.getMessage(), containsString("Not fully encoded, current state: " + expectedState)); + } + + private void modifyHeaderToLookLikeVersion0() + { + messageHeaderDecoder.wrap(buffer, OFFSET); + final int v1TemplateId = messageHeaderDecoder.templateId() + 1_000; + messageHeaderEncoder.wrap(buffer, OFFSET); + messageHeaderEncoder.templateId(v1TemplateId).version(0); + } + + private void modifyHeaderToLookLikeVersion1() + { + messageHeaderDecoder.wrap(buffer, OFFSET); + assert messageHeaderDecoder.version() == 1; + final int v0TemplateId = messageHeaderDecoder.templateId() - 1_000; + messageHeaderEncoder.wrap(buffer, OFFSET); + messageHeaderEncoder.templateId(v0TemplateId); + } +} diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaGeneratorTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaGeneratorTest.java index f12fef3751..3f253ed778 100644 --- a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaGeneratorTest.java +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaGeneratorTest.java @@ -15,6 +15,12 @@ */ package uk.co.real_logic.sbe.generation.java; +import uk.co.real_logic.sbe.Tests; +import uk.co.real_logic.sbe.generation.common.PrecedenceChecks; +import uk.co.real_logic.sbe.ir.Ir; +import uk.co.real_logic.sbe.xml.IrGenerator; +import uk.co.real_logic.sbe.xml.MessageSchema; +import uk.co.real_logic.sbe.xml.ParserOptions; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; @@ -22,23 +28,23 @@ import org.agrona.generation.StringWriterOutputManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import uk.co.real_logic.sbe.Tests; -import uk.co.real_logic.sbe.ir.Ir; -import uk.co.real_logic.sbe.xml.IrGenerator; -import uk.co.real_logic.sbe.xml.MessageSchema; -import uk.co.real_logic.sbe.xml.ParserOptions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.ByteOrder; +import java.util.Arrays; import java.util.Map; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static uk.co.real_logic.sbe.generation.java.JavaGenerator.MESSAGE_HEADER_DECODER_TYPE; import static uk.co.real_logic.sbe.generation.java.ReflectionUtil.*; import static uk.co.real_logic.sbe.xml.XmlSchemaParser.parse; @@ -251,6 +257,37 @@ void shouldGenerateBasicMessage() throws Exception assertThat(msgFlyweight.toString(), startsWith("[Car]")); } + @Test + void shouldGenerateWithoutPrecedenceChecksByDefault() throws Exception + { + final PrecedenceChecks.Context context = new PrecedenceChecks.Context(); + final PrecedenceChecks precedenceChecks = PrecedenceChecks.newInstance(context); + generator(precedenceChecks).generate(); + + final Field field = Arrays.stream(compileCarEncoder().getDeclaredFields()) + .filter(f -> f.getName().equals(context.precedenceChecksFlagName())) + .findFirst() + .orElse(null); + + assertNull(field); + } + + @Test + void shouldGeneratePrecedenceChecksWhenEnabled() throws Exception + { + final PrecedenceChecks.Context context = new PrecedenceChecks.Context() + .shouldGeneratePrecedenceChecks(true); + final PrecedenceChecks precedenceChecks = PrecedenceChecks.newInstance(context); + generator(precedenceChecks).generate(); + + final Field field = Arrays.stream(compileCarEncoder().getDeclaredFields()) + .filter(f -> f.getName().equals(context.precedenceChecksFlagName())) + .findFirst() + .orElse(null); + + assertNotNull(field); + } + @Test void shouldGenerateRepeatingGroupDecoder() throws Exception { @@ -298,8 +335,12 @@ void shouldGenerateVarDataCodecs() throws Exception final Object encoder = wrap(buffer, compileCarEncoder().getConstructor().newInstance()); final Object decoder = getCarDecoder(buffer, encoder); + setEmptyFuelFiguresGroup(encoder); + setEmptyPerformanceFiguresGroup(encoder); setManufacturer(encoder, expectedManufacturer); + skipFuelFiguresGroup(decoder); + skipPerformanceFiguresGroup(decoder); final String manufacturer = getManufacturer(decoder); assertEquals(expectedManufacturer, manufacturer); @@ -402,28 +443,30 @@ void shouldGenerateGetFixedLengthStringUsingAppendable() throws Exception assertThat(result.toString(), is("R11R12")); } - @Test - void shouldGenerateGetVariableStringUsingAppendable() throws Exception + @ParameterizedTest + @ValueSource(strings = {"Red", "", "Red and Blue"}) + void shouldGenerateGetVariableStringUsingAppendable(final String color) throws Exception { final UnsafeBuffer buffer = new UnsafeBuffer(new byte[4096]); final StringBuilder result = new StringBuilder(); generator().generate(); final Object encoder = wrap(buffer, compileCarEncoder().getDeclaredConstructor().newInstance()); - final Object decoder = getCarDecoder(buffer, encoder); - set(encoder, "color", String.class, "Red"); - get(decoder, "color", result); - assertThat(result.toString(), is("Red")); + setEmptyFuelFiguresGroup(encoder); + setEmptyPerformanceFiguresGroup(encoder); + set(encoder, "manufacturer", String.class, "Bristol"); + set(encoder, "model", String.class, "Britannia"); + set(encoder, "activationCode", String.class, "12345"); + set(encoder, "color", String.class, color); - result.setLength(0); - set(encoder, "color", String.class, ""); - get(decoder, "color", result); - assertThat(result.toString(), is("")); - - result.setLength(0); - set(encoder, "color", String.class, "Red and Blue"); - get(decoder, "color", result); - assertThat(result.toString(), is("Red and Blue")); + final Object decoder = getCarDecoder(buffer, encoder); + skipFuelFiguresGroup(decoder); + skipPerformanceFiguresGroup(decoder); + assertThat(get(decoder, "manufacturer"), equalTo("Bristol")); + assertThat(get(decoder, "model"), equalTo("Britannia")); + assertThat(get(decoder, "activationCode"), equalTo("12345")); + assertThat(get(decoder, "color", result), equalTo(color.length())); + assertThat(result.toString(), equalTo(color)); } @Test @@ -664,6 +707,12 @@ private JavaGenerator generator() return new JavaGenerator(ir, BUFFER_NAME, READ_ONLY_BUFFER_NAME, false, false, false, outputManager); } + private JavaGenerator generator(final PrecedenceChecks precedenceChecks) + { + return new JavaGenerator(ir, BUFFER_NAME, READ_ONLY_BUFFER_NAME, false, false, false, false, + precedenceChecks, outputManager); + } + private void generateTypeStubs() throws IOException { final JavaGenerator javaGenerator = generator(); diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/ReflectionUtil.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/ReflectionUtil.java index a48074eb89..beead1799e 100644 --- a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/ReflectionUtil.java +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/ReflectionUtil.java @@ -41,10 +41,30 @@ static Object get(final Object object, final String fieldName) throws Exception return object.getClass().getMethod(fieldName).invoke(object); } - static void get(final Object object, final String fieldName, final Appendable arg) throws Exception + static Object get(final Object object, final String fieldName, final Appendable arg) throws Exception { final String methodName = "get" + Generators.toUpperFirstChar(fieldName); - object.getClass().getMethod(methodName, Appendable.class).invoke(object, arg); + return object.getClass().getMethod(methodName, Appendable.class).invoke(object, arg); + } + + static void skipFuelFiguresGroup(final Object decoder) throws Exception + { + skipGroup(decoder, "fuelFigures"); + } + + static void skipPerformanceFiguresGroup(final Object decoder) throws Exception + { + skipGroup(decoder, "performanceFigures"); + } + + private static void skipGroup(final Object decoder, final String groupName) throws Exception + { + final Object group = get(decoder, groupName); + while ((boolean)get(group, "hasNext")) + { + get(group, "next"); + get(group, "sbeSkip"); + } } static String getManufacturer(final Object decoder) throws Exception @@ -52,6 +72,16 @@ static String getManufacturer(final Object decoder) throws Exception return (String)get(decoder, "manufacturer"); } + static void setEmptyFuelFiguresGroup(final Object encoder) throws Exception + { + encoder.getClass().getMethod("fuelFiguresCount", int.class).invoke(encoder, 0); + } + + static void setEmptyPerformanceFiguresGroup(final Object encoder) throws Exception + { + encoder.getClass().getMethod("performanceFiguresCount", int.class).invoke(encoder, 0); + } + static void setManufacturer(final Object encoder, final String value) throws Exception { encoder.getClass().getMethod("manufacturer", String.class).invoke(encoder, value); diff --git a/sbe-tool/src/test/resources/field-order-check-schema.xml b/sbe-tool/src/test/resources/field-order-check-schema.xml new file mode 100644 index 0000000000..e4d62ebfd6 --- /dev/null +++ b/sbe-tool/src/test/resources/field-order-check-schema.xml @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + 1 + 2 + + + + 1 + 2 + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +