All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uk.co.real_logic.sbe.generation.csharp.CSharpGenerator Maven / Gradle / Ivy

Go to download

FIX/SBE - OSI layer 6 presentation for encoding and decoding application messages in binary format for low-latency applications.

The newest version!
/*
 * Copyright 2013-2024 Real Logic Limited.
 * Copyright (C) 2017 MarketFactory, Inc
 *
 * 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.csharp;

import uk.co.real_logic.sbe.PrimitiveType;
import uk.co.real_logic.sbe.PrimitiveValue;
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.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.*;

/**
 * Codec generator for the CSharp programming language.
 */
@SuppressWarnings("MethodLength")
public class CSharpGenerator implements CodeGenerator
{
    private static final String META_ATTRIBUTE_ENUM = "MetaAttribute";
    private static final String INDENT = "    ";
    private static final String TWO_INDENT = INDENT + INDENT;
    private static final String THREE_INDENT = INDENT + INDENT + INDENT;
    private static final String BASE_INDENT = INDENT;

    private final Ir ir;
    private final OutputManager outputManager;
    private final PrecedenceChecks precedenceChecks;
    private final String precedenceChecksFlagName;

    /**
     * Create a new C# language {@link CodeGenerator}.
     *
     * @param ir            for the messages and types.
     * @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;
    }

    /**
     * Generate the composites for dealing with the message header.
     *
     * @throws IOException if an error is encountered when writing the output.
     */
    public void generateMessageHeaderStub() throws IOException
    {
        generateComposite(ir.headerStructure().tokens());
    }

    /**
     * Generate the stubs for the types used as message fields.
     *
     * @throws IOException if an error is encountered when writing the output.
     */
    public void generateTypeStubs() throws IOException
    {
        generateMetaAttributeEnum();

        for (final List tokens : ir.types())
        {
            switch (tokens.get(0).signal())
            {
                case BEGIN_ENUM:
                    generateEnum(tokens);
                    break;

                case BEGIN_SET:
                    generateBitSet(tokens);
                    break;

                case BEGIN_COMPOSITE:
                    generateComposite(tokens);
                    break;

                default:
                    break;
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void generate() throws IOException
    {
        generateMessageHeaderStub();
        generateTypeStubs();

        for (final List tokens : ir.messages())
        {
            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))
            {
                final List messageBody = tokens.subList(1, tokens.size() - 1);
                int offset = 0;
                final List fields = new ArrayList<>();
                offset = collectFields(messageBody, offset, fields);
                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, fieldPrecedenceModel, BASE_INDENT);
                out.append(sb);

                out.append(generateVarData(fieldPrecedenceModel, varData, BASE_INDENT + INDENT));

                out.append(generateDisplay(toUpperFirstChar(msgToken.name()),
                    className, fields, groups, varData, fieldPrecedenceModel));

                out.append(INDENT + "}\n");
                out.append("}\n");
            }
        }
    }

    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++)
        {
            final Token groupToken = tokens.get(i);
            if (groupToken.signal() != Signal.BEGIN_GROUP)
            {
                throw new IllegalStateException("tokens must begin with BEGIN_GROUP: token=" + groupToken);
            }
            final String groupName = groupToken.name();
            sb.append(generateGroupProperty(groupName, fieldPrecedenceModel, groupToken, 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(fieldPrecedenceModel, fields, indent + INDENT));

            final List groups = new ArrayList<>();
            i = collectGroups(tokens, i, groups);
            generateGroups(sb, parentMessageClassName, groups, fieldPrecedenceModel, indent + INDENT);

            final List varData = new ArrayList<>();
            i = collectVarData(tokens, i, varData);
            sb.append(generateVarData(fieldPrecedenceModel, varData, indent + INDENT + INDENT));

            appendGroupInstanceDisplay(sb, fields, groups, varData, indent + TWO_INDENT);

            sb.append(indent).append(INDENT + "}\n");
        }
    }

    private void generateGroupClassHeader(
        final StringBuilder sb,
        final String groupName,
        final String parentMessageClassName,
        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();

        sb.append(String.format("\n" +
            "%1$s" +
            indent + "public sealed partial class %2$sGroup\n" +
            indent + "{\n" +
            indent + INDENT + "private readonly %3$s _dimensions = new %3$s();\n" +
            indent + INDENT + "private %4$s _parentMessage;\n" +
            indent + INDENT + "private DirectBuffer _buffer;\n" +
            indent + INDENT + "private int _blockLength;\n" +
            indent + INDENT + "private int _actingVersion;\n" +
            indent + INDENT + "private int _count;\n" +
            indent + INDENT + "private int _index;\n" +
            indent + INDENT + "private int _offset;\n",
            generateDocumentation(indent, tokens.get(index)),
            formatClassName(groupName),
            dimensionsClassName,
            parentMessageClassName));

        final Token numInGroupToken = Generators.findFirst("numInGroup", tokens, index);
        final boolean isIntCastSafe = isRepresentableByInt32(numInGroupToken.encoding());

        if (!isIntCastSafe)
        {
            throw new IllegalArgumentException(String.format(
                "%s.numInGroup - cannot be represented safely by an int. Please constrain the maxValue.",
                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" +
            indent + INDENT + INDENT + "_parentMessage = parentMessage;\n" +
            indent + INDENT + INDENT + "_buffer = buffer;\n" +
            indent + INDENT + INDENT + "_dimensions.Wrap(buffer, parentMessage.Limit, actingVersion);\n" +
            indent + INDENT + INDENT + "_parentMessage.Limit = parentMessage.Limit + SbeHeaderSize;\n" +
            indent + INDENT + INDENT + "_blockLength = _dimensions.BlockLength;\n" +
            indent + INDENT + INDENT + "_count = (int) _dimensions.NumInGroup;\n" + // cast safety checked above
            indent + INDENT + INDENT + "_actingVersion = actingVersion;\n" +
            indent + INDENT + INDENT + "_index = 0;\n" +
            indent + INDENT + "}\n",
            parentMessageClassName));

        final int blockLength = tokens.get(index).encodedLength();
        final String typeForBlockLength = cSharpTypeName(tokens.get(index + 2).encoding().primitiveType());
        final String typeForNumInGroup = cSharpTypeName(numInGroupToken.encoding().primitiveType());

        final String throwCondition = numInGroupToken.encoding().applicableMinValue().longValue() == 0 ?
            "if ((uint) count > %3$d)\n" :
            "if (count < %2$d || count > %3$d)\n";

        sb.append(String.format("\n" +
            indent + INDENT + "public void WrapForEncode(%1$s parentMessage, DirectBuffer buffer, int count)\n" +
            indent + INDENT + "{\n" +
            indent + INDENT + INDENT + throwCondition +
            indent + INDENT + INDENT + "{\n" +
            indent + INDENT + INDENT + INDENT + "ThrowHelper.ThrowCountOutOfRangeException(count);\n" +
            indent + INDENT + INDENT + "}\n\n" +
            indent + INDENT + INDENT + "_parentMessage = parentMessage;\n" +
            indent + INDENT + INDENT + "_buffer = buffer;\n" +
            indent + INDENT + INDENT + "_dimensions.Wrap(buffer, parentMessage.Limit, SchemaVersion);\n" +
            indent + INDENT + INDENT + "parentMessage.Limit = parentMessage.Limit + SbeHeaderSize;\n" +
            indent + INDENT + INDENT + "_dimensions.BlockLength = SbeBlockLength;\n" +
            indent + INDENT + INDENT + "_dimensions.NumInGroup = (%5$s) count;\n" +
            indent + INDENT + INDENT + "_index = 0;\n" +
            indent + INDENT + INDENT + "_count = count;\n" +
            indent + INDENT + INDENT + "_blockLength = SbeBlockLength;\n" +
            indent + INDENT + INDENT + "_actingVersion = SchemaVersion;\n" +
            indent + INDENT + "}\n",
            parentMessageClassName,
            numInGroupToken.encoding().applicableMinValue().longValue(),
            numInGroupToken.encoding().applicableMaxValue().longValue(),
            typeForBlockLength,
            typeForNumInGroup));

        sb.append(String.format("\n" +
            indent + INDENT + "public const int SbeBlockLength = %d;\n" +
            indent + INDENT + "public const int SbeHeaderSize = %d;\n",
            blockLength,
            dimensionHeaderLength));

        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\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" +
            indent + INDENT + "public %sGroup Next()\n" +
            indent + INDENT + "{\n" +
            indent + INDENT + INDENT + "if (_index >= _count)\n" +
            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" +
            indent + INDENT + INDENT + "return this;\n" +
            indent + INDENT + "}\n",
            formatClassName(groupName)));

        sb.append("\n" +
            indent + INDENT + "public System.Collections.IEnumerator GetEnumerator()\n" +
            indent + INDENT + "{\n" +
            indent + INDENT + INDENT + "while (this.HasNext)\n" +
            indent + INDENT + INDENT + "{\n" +
            indent + INDENT + INDENT + INDENT + "yield return this.Next();\n" +
            indent + INDENT + INDENT + "}\n" +
            indent + INDENT + "}\n");
    }

    private boolean isRepresentableByInt32(final Encoding encoding)
    {
        // These min and max values are the same in .NET
        return encoding.applicableMinValue().longValue() >= Integer.MIN_VALUE &&
            encoding.applicableMaxValue().longValue() <= Integer.MAX_VALUE;
    }

    private CharSequence generateGroupProperty(
        final String groupName,
        final FieldPrecedenceModel fieldPrecedenceModel,
        final Token token,
        final String indent)
    {
        final StringBuilder sb = new StringBuilder();

        final String className = CSharpUtil.formatClassName(groupName);

        sb.append(String.format("\n" +
            indent + "private readonly %sGroup _%s = new %sGroup();\n",
            className,
            toLowerFirstChar(groupName),
            className));

        sb.append(String.format("\n" +
            indent + "public const long %sId = %d;\n",
            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),
            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),
            accessOrderListenerCallOnEncode));

        return sb;
    }

    private CharSequence generateVarData(
        final FieldPrecedenceModel fieldPrecedenceModel,
        final List tokens,
        final String indent)
    {
        final StringBuilder sb = new StringBuilder();

        for (int i = 0, size = tokens.size(); i < size; i++)
        {
            final Token token = tokens.get(i);
            if (token.signal() == Signal.BEGIN_VAR_DATA)
            {
                generateFieldIdMethod(sb, token, indent);
                generateSinceActingDeprecated(sb, indent, CSharpUtil.formatPropertyName(token.name()), token);
                generateOffsetMethod(sb, token, indent);

                final Token varDataToken = Generators.findFirst("varData", tokens, i);
                final String characterEncoding = varDataToken.encoding().characterEncoding();
                generateCharacterEncodingMethod(sb, token.name(), characterEncoding, indent);
                generateFieldMetaAttributeMethod(sb, token, indent);

                final String propertyName = toUpperFirstChar(token.name());
                final Token lengthToken = Generators.findFirst("length", tokens, i);
                final int sizeOfLengthField = lengthToken.encodedLength();
                final Encoding lengthEncoding = lengthToken.encoding();
                final String lengthCSharpType = cSharpTypeName(lengthEncoding.primitiveType());
                final String lengthTypePrefix = toUpperFirstChar(lengthEncoding.primitiveType().primitiveName());
                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,
                    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" +
                    indent + INDENT + "Get%1$s(new Span(dst, dstOffset, length));\n",
                    propertyName));

                sb.append(String.format("\n" +
                    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" +
                    indent + INDENT + "int dataLength = (int)_buffer.%4$sGet%5$s(limit);\n" +
                    indent + INDENT + "int bytesCopied = Math.Min(dst.Length, dataLength);\n" +
                    indent + INDENT + "_parentMessage.Limit = limit + sizeOfLengthField + dataLength;\n" +
                    indent + INDENT + "_buffer.GetBytes(limit + sizeOfLengthField, dst.Slice(0, bytesCopied));\n\n" +
                    indent + INDENT + "return bytesCopied;\n" +
                    indent + "}\n",
                    propertyName,
                    generateArrayFieldNotPresentCondition(token.version(), indent, "0"),
                    sizeOfLengthField,
                    lengthTypePrefix,
                    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" +
                    indent + INDENT + "int dataLength = (int)_buffer.%3$sGet%4$s(limit);\n" +
                    indent + INDENT + "byte[] data = new byte[dataLength];\n" +
                    indent + INDENT + "_parentMessage.Limit = limit + sizeOfLengthField + dataLength;\n" +
                    indent + INDENT + "_buffer.GetBytes(limit + sizeOfLengthField, data);\n\n" +
                    indent + INDENT + "return data;\n" +
                    indent + "}\n",
                    propertyName,
                    sizeOfLengthField,
                    lengthTypePrefix,
                    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" +
                    indent + INDENT + "Set%1$s(new ReadOnlySpan(src, srcOffset, length));\n",
                    propertyName));

                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" +
                    indent + INDENT + "_buffer.%3$sPut%5$s(limit, (%4$s)src.Length);\n" +
                    indent + INDENT + "_buffer.SetBytes(limit + sizeOfLengthField, src);\n\n" +
                    indent + INDENT + "return src.Length;\n" +
                    indent + "}\n",
                    propertyName,
                    sizeOfLengthField,
                    lengthTypePrefix,
                    lengthCSharpType,
                    byteOrderStr,
                    accessOrderListenerCall));

                if (characterEncoding != null)  // only generate these string based methods if there is an encoding
                {
                    sb.append(lineSeparator())
                        .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" +
                        indent + INDENT + "int dataLength = (int)_buffer.%3$sGet%4$s(limit);\n" +
                        indent + INDENT + "_parentMessage.Limit = limit + sizeOfLengthField + dataLength;\n" +
                        indent + INDENT + "return _buffer.GetStringFromBytes(%1$sResolvedCharacterEncoding," +
                        " limit + sizeOfLengthField, dataLength);\n" +
                        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, (%5$s)byteCount);\n" +
                        indent + "}\n",
                        propertyName,
                        sizeOfLengthField,
                        lengthTypePrefix,
                        byteOrderStr,
                        lengthCSharpType,
                        generateArrayFieldNotPresentCondition(token.version(), indent, "\"\""),
                        accessOrderListenerCall));
                }
            }
        }

        return sb;
    }

    private void generateBitSet(final List tokens) throws IOException
    {
        final Token enumToken = tokens.get(0);
        final String enumName = CSharpUtil.formatClassName(enumToken.applicableTypeName());

        try (Writer out = outputManager.createOutput(enumName))
        {
            out.append(generateFileHeader(ir.applicableNamespace()));
            out.append(generateDocumentation(INDENT, enumToken));
            final String enumPrimitiveType = cSharpTypeName(enumToken.encoding().primitiveType());
            out.append(generateEnumDeclaration(enumName, enumPrimitiveType, true));

            out.append(generateChoices(tokens.subList(1, tokens.size() - 1)));

            out.append(INDENT + "}\n");
            out.append(generateChoiceDisplay(enumName));
            out.append("}\n");
        }
    }

    private void generateEnum(final List tokens) throws IOException
    {
        final Token enumToken = tokens.get(0);
        final String enumName = CSharpUtil.formatClassName(enumToken.applicableTypeName());

        try (Writer out = outputManager.createOutput(enumName))
        {
            out.append(generateFileHeader(ir.applicableNamespace()));
            out.append(generateDocumentation(INDENT, enumToken));
            final String enumPrimitiveType = cSharpTypeName(enumToken.encoding().primitiveType());
            out.append(generateEnumDeclaration(enumName, enumPrimitiveType, false));

            out.append(generateEnumValues(tokens.subList(1, tokens.size() - 1), enumToken));

            out.append(INDENT + "}\n");
            out.append("}\n");
        }
    }

    private void generateComposite(final List tokens) throws IOException
    {
        final String compositeName = CSharpUtil.formatClassName(tokens.get(0).applicableTypeName());

        try (Writer out = outputManager.createOutput(compositeName))
        {
            out.append(generateFileHeader(ir.applicableNamespace()));
            out.append(generateDocumentation(INDENT, tokens.get(0)));
            out.append(generateClassDeclaration(compositeName));
            out.append(generateFixedFlyweightCode(tokens.get(0).encodedLength()));
            out.append(generateCompositePropertyElements(tokens.subList(1, tokens.size() - 1), BASE_INDENT));

            out.append(generateCompositeDisplay(tokens));
            out.append(INDENT + "}\n");
            out.append("}\n");
        }
    }

    private CharSequence generateCompositePropertyElements(final List tokens, final String indent)
    {
        final StringBuilder sb = new StringBuilder();

        for (int i = 0; i < tokens.size();)
        {
            final Token token = tokens.get(i);
            final String propertyName = formatPropertyName(token.name());

            switch (token.signal())
            {
                case ENCODING:
                    sb.append(generatePrimitiveProperty(propertyName, token, token, null, indent));
                    break;

                case BEGIN_ENUM:
                    sb.append(generateEnumProperty(propertyName, token, token, null, indent));
                    break;

                case BEGIN_SET:
                    sb.append(generateBitSetProperty(propertyName, token, token, null, indent));
                    break;

                case BEGIN_COMPOSITE:
                    sb.append(generateCompositeProperty(propertyName, token, token, null, indent));
                    break;

                default:
                    break;
            }

            i += tokens.get(i).componentTokenCount();
        }

        return sb;
    }

    private CharSequence generateChoices(final List tokens)
    {
        final StringBuilder sb = new StringBuilder();

        for (final Token token : tokens)
        {
            if (token.signal() == Signal.CHOICE)
            {
                final String choiceName = toUpperFirstChar(token.applicableTypeName());
                final String choiceBitPosition = token.encoding().constValue().toString();
                final int choiceValue = (int)Math.pow(2, Integer.parseInt(choiceBitPosition));
                sb.append(String.format(INDENT + INDENT + "%s = %s,\n", choiceName, choiceValue));
            }
        }

        return sb;
    }

    private CharSequence generateEnumValues(final List tokens, final Token encodingToken)
    {
        final StringBuilder sb = new StringBuilder();
        final Encoding encoding = encodingToken.encoding();

        for (final Token token : tokens)
        {
            sb.append(generateDocumentation(INDENT + INDENT, token))
              .append(INDENT).append(INDENT).append(formatForCSharpKeyword(token.name())).append(" = ")
              .append(token.encoding().constValue()).append(",\n");
        }

        final PrimitiveValue nullVal = encoding.applicableNullValue();

        sb.append(INDENT).append(INDENT).append("NULL_VALUE = ").append(nullVal).append("\n");

        return sb;
    }

    private CharSequence generateClassDeclaration(final String className)
    {
        return String.format(
            INDENT + "public sealed partial class %s\n" +
            INDENT + "{\n",
            className);
    }

    private void generateMetaAttributeEnum() throws IOException
    {
        try (Writer out = outputManager.createOutput(META_ATTRIBUTE_ENUM))
        {
            out.append(generateFileHeader(ir.applicableNamespace()));

            out.append(
                INDENT + "public enum MetaAttribute\n" +
                INDENT + "{\n" +
                INDENT + INDENT + "Epoch,\n" +
                INDENT + INDENT + "TimeUnit,\n" +
                INDENT + INDENT + "SemanticType,\n" +
                INDENT + INDENT + "Presence\n" +
                INDENT + "}\n" +
                "}\n");
        }
    }

    private CharSequence generateEnumDeclaration(
        final String name,
        final String primitiveType,
        final boolean addFlagsAttribute)
    {
        String result = "";
        if (addFlagsAttribute)
        {
            result += INDENT + "[Flags]\n";
        }

        result +=
            INDENT + "public enum " + name + " : " + primitiveType + "\n" +
            INDENT + "{\n";

        return result;
    }

    private CharSequence generatePrimitiveProperty(
        final String propertyName,
        final Token fieldToken,
        final Token typeToken,
        final FieldPrecedenceModel fieldPrecedenceModel,
        final String indent)
    {
        final StringBuilder sb = new StringBuilder();

        sb.append(generatePrimitiveFieldMetaData(propertyName, typeToken, indent + INDENT));

        if (typeToken.isConstantEncoding())
        {
            sb.append(generateConstPropertyMethods(propertyName, typeToken, indent));
        }
        else
        {
            sb.append(generatePrimitivePropertyMethods(propertyName, fieldToken, typeToken,
                fieldPrecedenceModel, indent));
        }

        return sb;
    }

    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,
                fieldPrecedenceModel, indent + INDENT);
        }
        else if (arrayLength > 1)
        {
            return generateArrayProperty(propertyName, fieldToken, typeToken,
                fieldPrecedenceModel, indent + INDENT);
        }

        return "";
    }

    private CharSequence generatePrimitiveFieldMetaData(
        final String propertyName,
        final Token token,
        final String indent)
    {
        final PrimitiveType primitiveType = token.encoding().primitiveType();
        final String typeName = cSharpTypeName(primitiveType);

        return String.format(
            "\n" +
            indent + "public const %1$s %2$sNullValue = %3$s;\n" +
            indent + "public const %1$s %2$sMinValue = %4$s;\n" +
            indent + "public const %1$s %2$sMaxValue = %5$s;\n",
            typeName,
            toUpperFirstChar(propertyName),
            generateLiteral(primitiveType, token.encoding().applicableNullValue().toString()),
            generateLiteral(primitiveType, token.encoding().applicableMinValue().toString()),
            generateLiteral(primitiveType, token.encoding().applicableMaxValue().toString()));
    }

    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());
        final String typePrefix = toUpperFirstChar(typeToken.encoding().primitiveType().primitiveName());
        final int offset = typeToken.offset();
        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" +
            indent + "{\n" +
            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",
            generateDocumentation(indent, fieldToken),
            typeName,
            toUpperFirstChar(propertyName),
            generateFieldNotPresentCondition(fieldToken.version(), typeToken.encoding(), indent),
            typePrefix,
            offset,
            byteOrderStr,
            accessOrderListenerCall);
    }

    private CharSequence generateFieldNotPresentCondition(
        final int sinceVersion,
        final Encoding encoding,
        final String indent)
    {
        if (0 == sinceVersion)
        {
            return "";
        }

        final String literal;
        if (sinceVersion > 0)
        {
            literal = generateLiteral(encoding.primitiveType(), encoding.applicableNullValue().toString());
        }
        else
        {
            literal = "(byte)0";
        }

        return String.format(
            indent + INDENT + INDENT + "if (_actingVersion < %1$d) return %2$s;\n\n",
            sinceVersion,
            literal);
    }

    private CharSequence generateGroupNotPresentCondition(
        final int sinceVersion,
        final String indent,
        final String groupInstanceField)
    {
        if (0 == sinceVersion)
        {
            return "";
        }

        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(
        final int sinceVersion,
        final String indent,
        final String bitSetName)
    {
        if (0 == sinceVersion)
        {
            return "";
        }

        return String.format(
            indent + INDENT + INDENT + INDENT + "if (_actingVersion < %1$d) return (%2$s)0;\n\n",
            sinceVersion,
            bitSetName);
    }

    private CharSequence generateTypeFieldNotPresentCondition(
        final int sinceVersion,
        final String indent)
    {
        if (0 == sinceVersion)
        {
            return "";
        }

        return String.format(
            indent + INDENT + INDENT + "if (_actingVersion < %d) return null;\n\n",
            sinceVersion);
    }

    private CharSequence generateArrayProperty(
        final String propertyName,
        final Token fieldToken,
        final Token typeToken,
        final FieldPrecedenceModel fieldPrecedenceModel, final String indent)
    {
        final String typeName = cSharpTypeName(typeToken.encoding().primitiveType());
        final String typePrefix = toUpperFirstChar(typeToken.encoding().primitiveType().primitiveName());
        final int offset = typeToken.offset();
        final ByteOrder byteOrder = typeToken.encoding().byteOrder();
        final String byteOrderStr = generateByteOrder(byteOrder, typeToken.encoding().primitiveType().size());
        final int fieldLength = typeToken.arrayLength();
        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" +
            indent + "public const int %sLength = %d;\n",
            propName, fieldLength));

        sb.append(String.format("\n" +
            "%1$s" +
            indent + "public %2$s Get%3$s(int index)\n" +
            indent + "{\n" +
            indent + INDENT + "if ((uint) index >= %4$d)\n" +
            indent + INDENT + "{\n" +
            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,
            accessOrderListenerCall));

        sb.append(String.format("\n" +
            "%1$s" +
            indent + "public void Set%2$s(int index, %3$s value)\n" +
            indent + "{\n" +
            indent + INDENT + "if ((uint) index >= %4$d)\n" +
            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,
            accessOrderListenerCall));

        sb.append(String.format("\n" +
            "%1$s" +
            indent + "public ReadOnlySpan<%2$s> %3$s\n" +
            indent + "{\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,
            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,
            generateArrayFieldNotPresentCondition(fieldToken.version(),
            indent + INDENT + INDENT, "new " + typeName + "[0]"),
            accessOrderListenerCall));

        if (typeToken.encoding().primitiveType() == PrimitiveType.CHAR)
        {
            generateCharacterEncodingMethod(sb, propertyName, typeToken.encoding().characterEncoding(), indent);

            sb.append(String.format("\n" +
                indent + "public int Get%1$s(byte[] dst, int dstOffset)\n" +
                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, "0"),
                accessOrderListenerCall));

            sb.append(String.format("\n" +
                indent + "public int Get%1$s(Span dst)\n" +
                indent + "{\n" +
                indent + INDENT + "const int length = %2$d;\n" +
                indent + INDENT + "if (dst.Length < length)\n" +
                indent + INDENT + "{\n" +
                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, "0"),
                offset,
                accessOrderListenerCall));

            sb.append(String.format("\n" +
                indent + "public void Set%1$s(byte[] src, int srcOffset)\n" +
                indent + "{\n" +
                indent + INDENT + "Set%1$s(new ReadOnlySpan(src, srcOffset, src.Length - srcOffset));\n" +
                indent + "}\n",
                propName, fieldLength, offset));

            sb.append(String.format("\n" +
                indent + "public void Set%1$s(ReadOnlySpan src)\n" +
                indent + "{\n" +
                indent + INDENT + "const int length = %2$d;\n" +
                indent + INDENT + "if (src.Length > length)\n" +
                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,
                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,
                accessOrderListenerCall));
        }

        return sb;
    }

    private void generateCharacterEncodingMethod(
        final StringBuilder sb,
        final String propertyName,
        final String encoding,
        final String indent)
    {
        if (encoding != null) // Raw data fields might not have encodings
        {
            sb.append(String.format("\n" +
                indent + "public const string %1$sCharacterEncoding = \"%2$s\";\n" +
                indent + "public static Encoding %1$sResolvedCharacterEncoding = " +
                "Encoding.GetEncoding(%1$sCharacterEncoding);\n\n",
                formatPropertyName(propertyName), encoding));
        }
    }

    private CharSequence generateConstPropertyMethods(
        final String propertyName,
        final Token token,
        final String indent)
    {
        if (token.encoding().primitiveType() != PrimitiveType.CHAR)
        {
            // ODE: we generate a property here because the constant could
            // become a field in a newer version of the protocol
            return String.format("\n" +
                "%1s" +
                indent + INDENT + "public %2$s %3$s { get { return %4$s; } }\n",
                generateDocumentation(indent + INDENT, token),
                cSharpTypeName(token.encoding().primitiveType()),
                toUpperFirstChar(propertyName),
                generateLiteral(token.encoding().primitiveType(), token.encoding().constValue().toString()));
        }

        final StringBuilder sb = new StringBuilder();

        final String csharpTypeName = cSharpTypeName(token.encoding().primitiveType());
        final byte[] constantValue = token.encoding().constValue().byteArrayValue(token.encoding().primitiveType());
        final CharSequence values = generateByteLiteralList(
            token.encoding().constValue().byteArrayValue(token.encoding().primitiveType()));

        sb.append(String.format(
            "\n" +
            indent + INDENT + "private static readonly byte[] _%1$sValue = { %2$s };\n",
            propertyName,
            values));

        sb.append(String.format(
            "\n" +
            indent + INDENT + "public const int %1$sLength = %2$d;\n",
            toUpperFirstChar(propertyName),
            constantValue.length));

        sb.append(String.format(
            indent + INDENT + "public %1$s %2$s(int index)\n" +
            indent + INDENT + "{\n" +
            indent + INDENT + INDENT + "return _%3$sValue[index];\n" +
            indent + INDENT + "}\n\n",
            csharpTypeName,
            toUpperFirstChar(propertyName),
            propertyName));

        sb.append(String.format(
            indent + INDENT + "public int Get%1$s(byte[] dst, int offset, int length)\n" +
            indent + INDENT + "{\n" +
            indent + INDENT + INDENT + "int bytesCopied = Math.Min(length, %2$d);\n" +
            indent + INDENT + INDENT + "Array.Copy(_%3$sValue, 0, dst, offset, bytesCopied);\n" +
            indent + INDENT + INDENT + "return bytesCopied;\n" +
            indent + INDENT + "}\n",
            toUpperFirstChar(propertyName),
            constantValue.length,
            propertyName));

        return sb;
    }

    private CharSequence generateByteLiteralList(final byte[] bytes)
    {
        final StringBuilder values = new StringBuilder();
        for (final byte b : bytes)
        {
            values.append(b).append(", ");
        }

        if (values.length() > 0)
        {
            values.setLength(values.length() - 2);
        }

        return values;
    }

    private CharSequence generateFixedFlyweightCode(final int size)
    {
        return String.format(
            INDENT + INDENT + "public const %1$s SbeSchemaId = %2$s;\n" +
            INDENT + INDENT + "public const %3$s SbeSchemaVersion = %4$s;\n" +
            INDENT + INDENT + "public const int Size = %5$d;\n\n" +

            INDENT + INDENT + "private DirectBuffer _buffer;\n" +
            INDENT + INDENT + "private int _offset;\n" +
            INDENT + INDENT + "private int _actingVersion;\n\n" +

            INDENT + INDENT + "public void Wrap(DirectBuffer buffer, int offset, int actingVersion)\n" +
            INDENT + INDENT + "{\n" +
            INDENT + INDENT + INDENT + "_offset = offset;\n" +
            INDENT + INDENT + INDENT + "_actingVersion = actingVersion;\n" +
            INDENT + INDENT + INDENT + "_buffer = buffer;\n" +
            INDENT + INDENT + "}\n\n",
            cSharpTypeName(ir.headerStructure().schemaIdType()),
            generateLiteral(ir.headerStructure().schemaIdType(), Integer.toString(ir.id())),
            cSharpTypeName(ir.headerStructure().schemaVersionType()),
            generateLiteral(ir.headerStructure().schemaVersionType(), Integer.toString(ir.version())),
            size);
    }

    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());
        final String schemaIdType = cSharpTypeName(ir.headerStructure().schemaIdType());
        final String schemaVersionType = cSharpTypeName(ir.headerStructure().schemaVersionType());
        final String semanticType = token.encoding().semanticType() == null ? "" : token.encoding().semanticType();
        final String semanticVersion = ir.semanticVersion() == null ? "" : ir.semanticVersion();

        return String.format(
            indent + INDENT + "public const %1$s BlockLength = %2$s;\n" +
            indent + INDENT + "public const %3$s TemplateId = %4$s;\n" +
            indent + INDENT + "public const %5$s SchemaId = %6$s;\n" +
            indent + INDENT + "public const %7$s SchemaVersion = %8$s;\n" +
            indent + INDENT + "public const string SemanticType = \"%9$s\";\n" +
            indent + INDENT + "public const string SemanticVersion = \"%11$s\";\n\n" +
            indent + INDENT + "private readonly %10$s _parentMessage;\n" +
            indent + INDENT + "private DirectBuffer _buffer;\n" +
            indent + INDENT + "private int _offset;\n" +
            indent + INDENT + "private int _limit;\n" +
            indent + INDENT + "private int _actingBlockLength;\n" +
            indent + INDENT + "private int _actingVersion;\n" +
            "\n" +
            indent + INDENT + "public int Offset { get { return _offset; } }\n\n" +
            indent + INDENT + "public %10$s()\n" +
            indent + INDENT + "{\n" +
            indent + INDENT + INDENT + "_parentMessage = this;\n" +
            indent + INDENT + "}\n\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 %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" +
            indent + INDENT + INDENT + "headerEncoder.TemplateId = TemplateId;\n" +
            indent + INDENT + INDENT + "headerEncoder.SchemaId = SchemaId;\n" +
            indent + INDENT + INDENT + "headerEncoder.Version = SchemaVersion;\n" +
            indent + INDENT + INDENT + "\n" +
            indent + INDENT + INDENT + "return WrapForEncode(buffer, offset + MessageHeader.Size);\n" +
            indent + INDENT + "}\n\n" +
            "%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" +
            indent + INDENT + INDENT + "get\n" +
            indent + INDENT + INDENT + "{\n" +
            indent + INDENT + INDENT + INDENT + "return _limit - _offset;\n" +
            indent + INDENT + INDENT + "}\n" +
            indent + INDENT + "}\n\n" +
            indent + INDENT + "public int Limit\n" +
            indent + INDENT + "{\n" +
            indent + INDENT + INDENT + "get\n" +
            indent + INDENT + INDENT + "{\n" +
            indent + INDENT + INDENT + INDENT + "return _limit;\n" +
            indent + INDENT + INDENT + "}\n" +
            indent + INDENT + INDENT + "set\n" +
            indent + INDENT + INDENT + "{\n" +
            indent + INDENT + INDENT + INDENT + "_buffer.CheckLimit(value);\n" +
            indent + INDENT + INDENT + INDENT + "_limit = value;\n" +
            indent + INDENT + INDENT + "}\n" +
            indent + INDENT + "}\n\n",
            blockLengthType,
            generateLiteral(ir.headerStructure().blockLengthType(), Integer.toString(token.encodedLength())),
            templateIdType,
            generateLiteral(ir.headerStructure().templateIdType(), Integer.toString(token.id())),
            schemaIdType,
            generateLiteral(ir.headerStructure().schemaIdType(), Integer.toString(ir.id())),
            schemaVersionType,
            generateLiteral(ir.headerStructure().schemaVersionType(), Integer.toString(ir.version())),
            semanticType,
            className,
            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 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");

        fieldPrecedenceModel.forEachWrappedStateByVersionDesc((version, state) ->
            sb.append(indent).append("    if (actingVersion >= ").append(version).append(")\n")
            .append(indent).append("    {\n")
            .append(indent).append("        codecState(")
            .append(qualifiedStateCase(state)).append(");\n")
            .append(indent).append("        return;\n")
            .append(indent).append("    }\n\n"));

        sb.append(indent)
            .append("    throw new InvalidOperationException(\"Unsupported acting version: \" + actingVersion);\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();

        for (int i = 0, size = tokens.size(); i < size; i++)
        {
            final Token signalToken = tokens.get(i);
            if (signalToken.signal() == Signal.BEGIN_FIELD)
            {
                final Token encodingToken = tokens.get(i + 1);
                final String propertyName = signalToken.name();

                generateFieldIdMethod(sb, signalToken, indent + INDENT);
                generateSinceActingDeprecated(
                    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,
                            fieldPrecedenceModel, indent));
                        break;

                    case BEGIN_ENUM:
                        sb.append(generateEnumProperty(propertyName, signalToken, encodingToken,
                            fieldPrecedenceModel, indent));
                        break;

                    case BEGIN_SET:
                        sb.append(generateBitSetProperty(propertyName, signalToken, encodingToken,
                            fieldPrecedenceModel, indent));
                        break;

                    case BEGIN_COMPOSITE:
                        sb.append(generateCompositeProperty(propertyName, signalToken, encodingToken,
                            fieldPrecedenceModel, indent));
                        break;

                    default:
                        break;
                }
            }
        }

        return sb;
    }

    private void generateFieldIdMethod(final StringBuilder sb, final Token token, final String indent)
    {
        sb.append(String.format("\n" +
            indent + "public const int %sId = %d;\n",
            CSharpUtil.formatPropertyName(token.name()),
            token.id()));
    }

    private void generateOffsetMethod(final StringBuilder sb, final Token token, final String indent)
    {
        sb.append(String.format("\n" +
            indent + "public const int %sOffset = %d;\n",
            CSharpUtil.formatPropertyName(token.name()),
            token.offset()));
    }

    private void generateFieldMetaAttributeMethod(final StringBuilder sb, final Token token, final String indent)
    {
        final Encoding encoding = token.encoding();
        final String epoch = encoding.epoch() == null ? "" : encoding.epoch();
        final String timeUnit = encoding.timeUnit() == null ? "" : encoding.timeUnit();
        final String semanticType = encoding.semanticType() == null ? "" : encoding.semanticType();
        final String presence = encoding.presence() == null ? "" : encoding.presence().toString().toLowerCase();

        sb.append(String.format("\n" +
            indent + "public static string %sMetaAttribute(MetaAttribute metaAttribute)\n" +
            indent + "{\n" +
            indent + INDENT + "switch (metaAttribute)\n" +
            indent + INDENT + "{\n" +
            indent + INDENT + INDENT + "case MetaAttribute.Epoch: return \"%s\";\n" +
            indent + INDENT + INDENT + "case MetaAttribute.TimeUnit: return \"%s\";\n" +
            indent + INDENT + INDENT + "case MetaAttribute.SemanticType: return \"%s\";\n" +
            indent + INDENT + INDENT + "case MetaAttribute.Presence: return \"%s\";\n" +
            indent + INDENT + "}\n\n" +
            indent + INDENT + "return \"\";\n" +
            indent + "}\n",
            toUpperFirstChar(token.name()),
            epoch,
            timeUnit,
            semanticType,
            presence));
    }

    private CharSequence generateEnumFieldNotPresentCondition(
        final int sinceVersion,
        final String enumName,
        final String indent)
    {
        if (0 == sinceVersion)
        {
            return "";
        }

        return String.format(
            indent + INDENT + INDENT + "if (_actingVersion < %d) return %s.NULL_VALUE;\n\n",
            sinceVersion,
            enumName);
    }

    private CharSequence generateEnumProperty(
        final String propertyName,
        final Token fieldToken,
        final Token typeToken,
        final FieldPrecedenceModel fieldPrecedenceModel,
        final String indent)
    {
        final String enumName = formatClassName(typeToken.applicableTypeName());
        final String typePrefix = toUpperFirstChar(typeToken.encoding().primitiveType().primitiveName());
        final String enumUnderlyingType = cSharpTypeName(typeToken.encoding().primitiveType());
        final int offset = typeToken.offset();
        final ByteOrder byteOrder = typeToken.encoding().byteOrder();
        final String byteOrderStr = generateByteOrder(byteOrder, typeToken.encoding().primitiveType().size());

        if (fieldToken.isConstantEncoding())
        {
            final String constValue = fieldToken.encoding().constValue().toString();

            return String.format("\n" +
                "%1$s" +
                indent + INDENT + "public %2$s %3$s\n" +
                indent + INDENT + "{\n" +
                indent + INDENT + INDENT + "get\n" +
                indent + INDENT + INDENT + "{\n" +
                indent + INDENT + INDENT + INDENT + "return %4$s;\n" +
                indent + INDENT + INDENT + "}\n" +
                indent + INDENT + "}\n\n",
                generateDocumentation(indent + INDENT, fieldToken),
                enumName,
                toUpperFirstChar(propertyName),
                constValue);
        }
        else
        {
            final CharSequence accessOrderListenerCall = generateAccessOrderListenerCall(
                fieldPrecedenceModel, indent + TWO_INDENT, fieldToken);

            return String.format("\n" +
                "%1$s" +
                indent + INDENT + "public %2$s %3$s\n" +
                indent + INDENT + "{\n" +
                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",
                generateDocumentation(indent + INDENT, fieldToken),
                enumName,
                toUpperFirstChar(propertyName),
                generateEnumFieldNotPresentCondition(fieldToken.version(), enumName, indent),
                enumName,
                typePrefix,
                offset,
                byteOrderStr,
                enumUnderlyingType,
                accessOrderListenerCall);
        }
    }

    private String generateBitSetProperty(
        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();
        final String typePrefix = toUpperFirstChar(typeToken.encoding().primitiveType().primitiveName());
        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" +
            indent + INDENT + "public %2$s %3$s\n" +
            indent + INDENT + "{\n" +
            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",
            generateDocumentation(indent + INDENT, fieldToken),
            bitSetName,
            toUpperFirstChar(propertyName),
            generateBitSetNotPresentCondition(fieldToken.version(), indent, bitSetName),
            bitSetName,
            typePrefix,
            offset,
            byteOrderStr,
            typeName,
            accessOrderListenerCall);
    }

    private Object generateCompositeProperty(
        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" +
            indent + INDENT + "private readonly %1$s _%2$s = new %3$s();\n",
            compositeName,
            toLowerFirstChar(propertyName),
            compositeName));

        sb.append(String.format("\n" +
            "%1$s" +
            indent + INDENT + "public %2$s %3$s\n" +
            indent + INDENT + "{\n" +
            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" +
            indent + INDENT + "}\n",
            generateDocumentation(indent + INDENT, fieldToken),
            compositeName,
            toUpperFirstChar(propertyName),
            generateTypeFieldNotPresentCondition(fieldToken.version(), indent),
            toLowerFirstChar(propertyName),
            offset,
            accessOrderListenerCall));

        return sb;
    }

    private void generateSinceActingDeprecated(
        final StringBuilder sb,
        final String indent,
        final String propertyName,
        final Token token)
    {
        sb.append(String.format(
            indent + "public const int %1$sSinceVersion = %2$d;\n" +
            indent + "public const int %1$sDeprecated = %3$d;\n" +
            indent + "public bool %1$sInActingVersion()\n" +
            indent + "{\n" +
            indent + INDENT + "return _actingVersion >= %1$sSinceVersion;\n" +
            indent + "}\n",
            propertyName,
            token.version(),
            token.deprecated()));
    }

    private String generateByteOrder(final ByteOrder byteOrder, final int primitiveTypeSize)
    {
        if (primitiveTypeSize == 1)
        {
            return "";
        }

        if ("BIG_ENDIAN".equals(byteOrder.toString()))
        {
            return "BigEndian";
        }

        return "LittleEndian";
    }

    private void appendGroupInstanceDisplay(
        final StringBuilder sb,
        final List fields,
        final List groups,
        final List varData,
        final String baseIndent)
    {
        final String indent = baseIndent + INDENT;

        sb.append('\n');
        append(sb, indent, "internal void BuildString(StringBuilder builder)");
        append(sb, indent, "{");
        append(sb, indent, "    if (_buffer == null)");
        append(sb, indent, "    {");
        append(sb, indent, "        return;");
        append(sb, indent, "    }");
        sb.append('\n');
        Separators.BEGIN_COMPOSITE.appendToGeneratedBuilder(sb, indent + INDENT);
        appendDisplay(sb, fields, groups, varData, indent + INDENT);
        Separators.END_COMPOSITE.appendToGeneratedBuilder(sb, indent + INDENT);
        sb.append('\n');
        append(sb, indent, "}");
    }

    private void appendDisplay(
        final StringBuilder sb,
        final List fields,
        final List groups,
        final List varData,
        final String indent)
    {
        int lengthBeforeLastGeneratedSeparator = -1;

        for (int i = 0, size = fields.size(); i < size;)
        {
            final Token fieldToken = fields.get(i);
            if (fieldToken.signal() == Signal.BEGIN_FIELD)
            {
                final Token encodingToken = fields.get(i + 1);
                final String fieldName = formatPropertyName(fieldToken.name());
                lengthBeforeLastGeneratedSeparator = writeTokenDisplay(fieldName, encodingToken, sb, indent);

                i += fieldToken.componentTokenCount();
            }
            else
            {
                i++;
            }
        }

        for (int i = 0, size = groups.size(); i < size; i++)
        {
            final Token groupToken = groups.get(i);
            if (groupToken.signal() != Signal.BEGIN_GROUP)
            {
                throw new IllegalStateException("tokens must begin with BEGIN_GROUP: token=" + groupToken);
            }

            final String groupName = formatPropertyName(groupToken.name());
            final String varName = formatVariableName(groupToken.name());

            append(
                sb, indent, "builder.Append(\"" + groupName + Separators.KEY_VALUE + Separators.BEGIN_GROUP + "\");");
            append(sb, indent, "var " + varName + " = this." + groupName + ";");
            append(sb, indent, "if (" + varName + ".Count > 0)");
            append(sb, indent, "{");
            append(sb, indent, "    var first = true;");
            append(sb, indent, "    while (" + varName + ".HasNext)");
            append(sb, indent, "    {");
            append(sb, indent, "        if (!first)");
            append(sb, indent, "        {");
            append(sb, indent, "            builder.Append(',');");
            append(sb, indent, "        }");
            append(sb, indent, "        first = false;");
            append(sb, indent, "        " + varName + ".Next().BuildString(builder);");
            append(sb, indent, "    }");
            append(sb, indent, "}");
            append(sb, indent, "builder.Append(\"" + Separators.END_GROUP + "\");");

            lengthBeforeLastGeneratedSeparator = sb.length();
            Separators.FIELD.appendToGeneratedBuilder(sb, indent);

            i = findEndSignal(groups, i, Signal.END_GROUP, groupToken.name());
        }

        for (int i = 0, size = varData.size(); i < size;)
        {
            final Token lengthToken = Generators.findFirst("length", varData, i);
            final int sizeOfLengthField = lengthToken.encodedLength();
            final Token varDataToken = varData.get(i);
            if (varDataToken.signal() != Signal.BEGIN_VAR_DATA)
            {
                throw new IllegalStateException("tokens must begin with BEGIN_VAR_DATA: token=" + varDataToken);
            }

            final String characterEncoding = varData.get(i + 3).encoding().characterEncoding();
            final String varDataName = formatPropertyName(varDataToken.name());
            final String getterName = formatGetterName(varDataToken.name());
            append(sb, indent, "builder.Append(\"" + varDataName + Separators.KEY_VALUE + "\");");
            if (characterEncoding == null)
            {
                final String name = Generators.toUpperFirstChar(varDataToken.name());
                append(sb, indent, "builder.Append(" + name + "Length()).Append(\" bytes of raw data\");");
                append(sb, indent, "_parentMessage.Limit = _parentMessage.Limit + " +
                    sizeOfLengthField + " + " + name + "Length();\n");
            }
            else
            {
                append(sb, indent, "builder.Append('\\'').Append(" + getterName + "()).Append('\\'');");
            }

            lengthBeforeLastGeneratedSeparator = sb.length();
            Separators.FIELD.appendToGeneratedBuilder(sb, indent);

            i += varDataToken.componentTokenCount();
        }

        if (lengthBeforeLastGeneratedSeparator > -1)
        {
            sb.setLength(lengthBeforeLastGeneratedSeparator);
        }
    }

    private int writeTokenDisplay(
        final String fieldName,
        final Token typeToken,
        final StringBuilder sb,
        final String indent)
    {
        if (typeToken.encodedLength() <= 0)
        {
            return -1;
        }

        append(sb, indent, "builder.Append(\"" + fieldName + Separators.KEY_VALUE + "\");");

        if (typeToken.isConstantEncoding())
        {
            append(sb, indent, "builder.Append(\"" + typeToken.encoding().constValue() + "\");");
        }
        else
        {
            switch (typeToken.signal())
            {
                case ENCODING:
                    if (typeToken.arrayLength() > 1)
                    {
                        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
                        {
                            Separators.BEGIN_ARRAY.appendToGeneratedBuilder(sb, indent);
                            append(sb, indent, "for (int i = 0; i < " + fieldName + "Length; ++i)");
                            append(sb, indent, "{");
                            append(sb, indent, "    if (i > 0)");
                            append(sb, indent, "    {");
                            append(sb, indent, "        builder.Append(',');");
                            append(sb, indent, "    }");
                            append(sb, indent, "    builder.Append(Get" + fieldName + "(i));");
                            append(sb, indent, "}");
                            Separators.END_ARRAY.appendToGeneratedBuilder(sb, indent);
                        }
                    }
                    else
                    {
                        append(sb, indent, "builder.Append(this." + fieldName + ");");
                    }
                    break;

                case BEGIN_ENUM:
                    append(sb, indent, "builder.Append(this." + fieldName + ");");
                    break;

                case BEGIN_SET:
                    append(sb, indent, "this." + fieldName + ".BuildString(builder);");
                    break;

                case BEGIN_COMPOSITE:
                    append(sb, indent, "if (this." + fieldName + " != null)");
                    append(sb, indent, "{");
                    append(sb, indent, "    this." + fieldName + ".BuildString(builder);");
                    append(sb, indent, "}");
                    append(sb, indent, "else");
                    append(sb, indent, "{");
                    append(sb, indent, "    builder.Append(\"null\");");
                    append(sb, indent, "}");
                    break;

                default:
                    break;
            }
        }

        final int lengthBeforeFieldSeparator = sb.length();
        Separators.FIELD.appendToGeneratedBuilder(sb, indent);

        return lengthBeforeFieldSeparator;
    }

    private void appendToString(final StringBuilder sb, final String indent)
    {
        sb.append('\n');
        append(sb, indent, "public override string ToString()");
        append(sb, indent, "{");
        append(sb, indent, "    var sb = new StringBuilder(100);");
        append(sb, indent, "    this.BuildString(sb);");
        append(sb, indent, "    return sb.ToString();");
        append(sb, indent, "}");
    }

    private void appendMessageToString(final StringBuilder sb, final String indent, final String className)
    {
        sb.append('\n');
        append(sb, indent, "public override string ToString()");
        append(sb, indent, "{");
        append(sb, indent, "    var sb = new StringBuilder(100);");
        append(sb, indent, "    var m = new " + className + "();");
        append(sb, indent, "    m.WrapForDecode(_buffer, _offset, _actingBlockLength, _actingVersion);");
        append(sb, indent, "    m.BuildString(sb);");
        append(sb, indent, "    return sb.ToString();");
        append(sb, indent, "}");
    }

    private CharSequence generateChoiceDisplay(final String enumName)
    {
        final StringBuilder sb = new StringBuilder();

        sb.append('\n');
        append(sb, INDENT, "static class " + enumName + "Ext");
        append(sb, INDENT, "{");
        append(sb, TWO_INDENT, "internal static void BuildString(this " + enumName + " val, StringBuilder builder)");
        append(sb, TWO_INDENT, "{");
        Separators.BEGIN_SET.appendToGeneratedBuilder(sb, THREE_INDENT);
        append(sb, THREE_INDENT, "builder.Append(val);");
        Separators.END_SET.appendToGeneratedBuilder(sb, THREE_INDENT);
        append(sb, TWO_INDENT, "}");
        append(sb, INDENT, "}");

        return sb;
    }

    private CharSequence generateDisplay(
        final String name,
        final String className,
        final List tokens,
        final List groups,
        final List varData,
        final FieldPrecedenceModel fieldPrecedenceModel)
    {
        final StringBuilder sb = new StringBuilder(100);

        appendMessageToString(sb, TWO_INDENT, className);
        sb.append('\n');
        append(sb, TWO_INDENT, "internal void BuildString(StringBuilder builder)");
        append(sb, TWO_INDENT, "{");
        append(sb, TWO_INDENT, "    if (_buffer == null)");
        append(sb, TWO_INDENT, "    {");
        append(sb, TWO_INDENT, "        throw new ArgumentNullException(\"_buffer\");");
        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);");
        append(sb, TWO_INDENT, "    builder.Append(\"|sbeSchemaId=\");");
        append(sb, TWO_INDENT, "    builder.Append(" + name + ".SchemaId);");
        append(sb, TWO_INDENT, "    builder.Append(\"|sbeSchemaVersion=\");");
        append(sb, TWO_INDENT, "    if (_parentMessage._actingVersion != " + name + ".SchemaVersion)");
        append(sb, TWO_INDENT, "    {");
        append(sb, TWO_INDENT, "        builder.Append(_parentMessage._actingVersion);");
        append(sb, TWO_INDENT, "        builder.Append('/');");
        append(sb, TWO_INDENT, "    }");
        append(sb, TWO_INDENT, "    builder.Append(" + name + ".SchemaVersion);");
        append(sb, TWO_INDENT, "    builder.Append(\"|sbeBlockLength=\");");
        append(sb, TWO_INDENT, "    if (_actingBlockLength != " + name + ".BlockLength)");
        append(sb, TWO_INDENT, "    {");
        append(sb, TWO_INDENT, "        builder.Append(_actingBlockLength);");
        append(sb, TWO_INDENT, "        builder.Append('/');");
        append(sb, TWO_INDENT, "    }");
        append(sb, TWO_INDENT, "    builder.Append(" + name + ".BlockLength);");
        append(sb, TWO_INDENT, "    builder.Append(\"):\");");
        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, "}");
        return sb;
    }

    private CharSequence generateCompositeDisplay(final List tokens)
    {
        final StringBuilder sb = new StringBuilder();

        appendToString(sb, TWO_INDENT);
        sb.append('\n');
        append(sb, TWO_INDENT, "internal void BuildString(StringBuilder builder)");
        append(sb, TWO_INDENT, "{");
        append(sb, TWO_INDENT, "    if (_buffer == null)");
        append(sb, TWO_INDENT, "    {");
        append(sb, TWO_INDENT, "        return;");
        append(sb, TWO_INDENT, "    }");
        sb.append('\n');
        Separators.BEGIN_COMPOSITE.appendToGeneratedBuilder(sb, THREE_INDENT);

        int lengthBeforeLastGeneratedSeparator = -1;

        for (int i = 1, end = tokens.size() - 1; i < end;)
        {
            final Token encodingToken = tokens.get(i);
            final String propertyName = formatPropertyName(encodingToken.name());
            lengthBeforeLastGeneratedSeparator = writeTokenDisplay(propertyName, encodingToken, sb, THREE_INDENT);
            i += encodingToken.componentTokenCount();
        }

        if (lengthBeforeLastGeneratedSeparator > -1)
        {
            sb.setLength(lengthBeforeLastGeneratedSeparator);
        }

        Separators.END_COMPOSITE.appendToGeneratedBuilder(sb, THREE_INDENT);
        sb.append('\n');
        append(sb, TWO_INDENT, "}");

        return sb;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy