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

uk.co.real_logic.sbe.generation.golang.GolangGenerator 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.

There is a newer version: 1.33.2
Show newest version
/*
 * Copyright 2013-2023 Real Logic Limited.
 * Copyright (C) 2016 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.golang;

import uk.co.real_logic.sbe.PrimitiveType;
import uk.co.real_logic.sbe.generation.CodeGenerator;
import org.agrona.generation.OutputManager;
import uk.co.real_logic.sbe.generation.Generators;
import uk.co.real_logic.sbe.generation.java.JavaUtil;
import uk.co.real_logic.sbe.ir.*;
import org.agrona.Verify;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;
import java.util.TreeSet;

import static uk.co.real_logic.sbe.PrimitiveType.CHAR;
import static uk.co.real_logic.sbe.generation.Generators.toUpperFirstChar;
import static uk.co.real_logic.sbe.generation.golang.GolangUtil.*;
import static uk.co.real_logic.sbe.ir.GenerationUtil.collectVarData;
import static uk.co.real_logic.sbe.ir.GenerationUtil.collectGroups;
import static uk.co.real_logic.sbe.ir.GenerationUtil.collectFields;

/**
 * Codec generator for the Go Lang programming language.
 */
@SuppressWarnings("MethodLength")
public class GolangGenerator implements CodeGenerator
{
    private final Ir ir;
    private final OutputManager outputManager;

    private final Stack> imports = new Stack<>();

    /**
     * Create a new Go language {@link CodeGenerator}.
     *
     * @param ir            for the messages and types.
     * @param outputManager for generating the codecs to.
     */
    public GolangGenerator(final Ir ir, final OutputManager outputManager)
    {
        Verify.notNull(ir, "ir");
        Verify.notNull(outputManager, "outputManager");

        this.ir = ir;
        this.outputManager = outputManager;
        this.imports.push(new TreeSet<>());  // ensure at least one
    }

    /**
     * Generate a file for the Ir based on a template.
     *
     * @param fileName     to generate.
     * @param templateName for the file.
     * @throws IOException if an error is encountered when writing the output.
     */
    public void generateFileFromTemplate(final String fileName, final String templateName) throws IOException
    {
        try (Writer out = outputManager.createOutput(fileName))
        {
            out.append(generateFromTemplate(ir.namespaces(), templateName));
        }
    }

    /**
     * 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
    {
        for (final List tokens : ir.types())
        {
            switch (tokens.get(0).signal())
            {
                case BEGIN_ENUM:
                    generateEnum(tokens);
                    break;

                case BEGIN_SET:
                    generateChoiceSet(tokens);
                    break;

                case BEGIN_COMPOSITE:
                    generateComposite(tokens, "");
                    break;

                default:
                    break;
            }
        }
    }

    // MessageHeader is special but the standard allows it to be
    // pretty arbitrary after the first four fields.
    // All we need is the imports, type declaration, and encode/decode.
    /**
     * 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
    {
        final String messageHeader = "MessageHeader";
        try (Writer out = outputManager.createOutput(messageHeader))
        {
            final StringBuilder sb = new StringBuilder();
            final List tokens = ir.headerStructure().tokens();

            imports.push(new TreeSet<>());
            imports.peek().add("io");

            generateTypeDeclaration(sb, messageHeader);
            generateTypeBodyComposite(sb, messageHeader, tokens.subList(1, tokens.size() - 1));

            generateEncodeDecode(sb, messageHeader, tokens.subList(1, tokens.size() - 1), false, false);
            generateCompositePropertyElements(sb, messageHeader, tokens.subList(1, tokens.size() - 1));
            out.append(generateFileHeader(ir.namespaces()));
            out.append(sb);
            imports.pop();
        }
    }

    /**
     * {@inheritDoc}
     */
    public void generate() throws IOException
    {
        // Add the Marshalling from the big or little endian
        // template kept inside the jar.
        //
        // The MessageHeader structure along with its en/decoding is
        // also in the templated SbeMarshalling.go
        //
        // final Token token = ir.messages().iterator().next().get(0);
        if (ir.byteOrder() == ByteOrder.LITTLE_ENDIAN)
        {
            generateFileFromTemplate("SbeMarshalling", "SbeMarshallingLittleEndian");
        }
        else
        {
            generateFileFromTemplate("SbeMarshalling", "SbeMarshallingBigEndian");
        }

        generateMessageHeaderStub();
        generateTypeStubs();

        for (final List tokens : ir.messages())
        {
            final Token msgToken = tokens.get(0);
            final String typeName = formatTypeName(msgToken.name());

            try (Writer out = outputManager.createOutput(typeName))
            {
                final StringBuilder sb = new StringBuilder();

                imports.push(new TreeSet<>());
                imports.peek().add("io");

                generateTypeDeclaration(sb, typeName);
                generateTypeBody(sb, typeName, tokens.subList(1, tokens.size() - 1));

                generateMessageCode(sb, typeName, tokens);

                final List messageBody = tokens.subList(1, tokens.size() - 1);
                int i = 0;

                final List fields = new ArrayList<>();
                i = collectFields(messageBody, i, fields);

                final List groups = new ArrayList<>();
                i = collectGroups(messageBody, i, groups);

                final List varData = new ArrayList<>();
                collectVarData(messageBody, i, varData);

                generateFields(sb, typeName, fields);
                generateGroups(sb, groups, typeName);
                generateGroupProperties(sb, groups, typeName);
                generateVarData(sb, typeName, varData);

                out.append(generateFileHeader(ir.namespaces()));
                out.append(sb);
                imports.pop();
            }
        }
    }

    private String generateEncodeOffset(final int gap, final String indent)
    {
        if (gap > 0)
        {
            return String.format("\n" +
                "%1$s\tfor i := 0; i < %2$d; i++ {\n" +
                "%1$s\t\tif err := _m.WriteUint8(_w, uint8(0)); err != nil {\n" +
                "%1$s\t\t\treturn err\n" +
                "%1$s\t\t}\n" +
                "%1$s\t}\n",
                indent,
                gap);
        }
        return "";
    }

    private String generateDecodeOffset(final int gap, final String indent)
    {
        if (gap > 0)
        {
            imports.peek().add("io");
            imports.peek().add("io/ioutil");
            return String.format("%1$s\tio.CopyN(ioutil.Discard, _r, %2$d)\n", indent, gap);
        }

        return "";
    }

    private void generateCharacterEncodingRangeCheck(
        final StringBuilder sb,
        final String varName,
        final Token token)
    {
        final String characterEncoding = token.encoding().characterEncoding();

        if (null != characterEncoding)
        {
            if (JavaUtil.isAsciiEncoding(characterEncoding))
            {
                imports.peek().add("fmt");
                sb.append(String.format(
                    "\tfor idx, ch := range %1$s {\n" +
                    "\t\tif ch > 127 {\n" +
                    "\t\t\treturn fmt.Errorf(\"%1$s[%%d]=%%d" +
                    " failed ASCII validation\", idx, ch)\n" +
                    "\t\t}\n" +
                    "\t}\n",
                    varName));
            }
            else if (JavaUtil.isUtf8Encoding(characterEncoding))
            {
                imports.peek().add("errors");
                imports.peek().add("unicode/utf8");
                sb.append(String.format(
                    "\tif !utf8.Valid(%1$s[:]) {\n" +
                    "\t\treturn errors.New(\"%1$s failed UTF-8 validation\")\n" +
                    "\t}\n",
                    varName));
            }
            else
            {
                throw new IllegalArgumentException("Unsupported encoding: " + characterEncoding);
            }
        }
    }

    private void generateEncodePrimitive(
        final StringBuilder sb,
        final char varName,
        final String propertyName,
        final Token encodingToken)
    {
        final PrimitiveType primitiveType = encodingToken.encoding().primitiveType();
        final String marshalType = golangMarshalType(primitiveType);

        // Complexity lurks here
        // A single character (byte) is handled as a uint8 (equivalent to byte)
        // An array of uint8 or byte is handled as Bytes (for speed)

        if (primitiveType == PrimitiveType.CHAR || primitiveType == PrimitiveType.UINT8)
        {
            if (encodingToken.arrayLength() > 1)
            {
                // byte or uint8 arrays get treated as Bytes
                // We take a slice to make the type right
                sb.append(String.format(
                    "\tif err := _m.WriteBytes(_w, %1$s.%2$s[:]); err != nil {\n" +
                    "\t\treturn err\n" +
                    "\t}\n",
                    varName,
                    propertyName));
            }
            else
            {
                // A single byte or uint8 gets treated as a uint8
                sb.append(String.format(
                    "\tif err := _m.WriteUint8(_w, %1$s.%2$s); err != nil {\n" +
                    "\t\treturn err\n" +
                    "\t}\n",
                    varName,
                    propertyName));
            }
        }
        else
        {
            if (encodingToken.arrayLength() > 1)
            {
                // Other array types need a for loop
                sb.append(String.format(
                    "\tfor idx := 0; idx < %1$d; idx++ {\n" +
                    "\t\tif err := _m.Write%2$s(_w, %3$s.%4$s[idx]); err != nil {\n" +
                    "\t\t\treturn err\n" +
                    "\t\t}\n" +
                    "\t}\n",
                    encodingToken.arrayLength(),
                    marshalType,
                    varName,
                    propertyName));
            }
            else
            {
                sb.append(String.format(
                    "\tif err := _m.Write%1$s(_w, %2$s.%3$s); err != nil {\n" +
                    "\t\treturn err\n" +
                    "\t}\n",
                    marshalType,
                    varName,
                    propertyName));
            }
        }
    }

    private void generateDecodePrimitive(final StringBuilder sb, final String varName, final Token token)
    {
        final PrimitiveType primitiveType = token.encoding().primitiveType();
        final String marshalType = golangMarshalType(primitiveType);

        // Complexity lurks here
        // A single character (byte) is handled as a uint8 (equivalent to byte)
        // An array of uint8 or byte is handled as Bytes (for speed)
        // And then we need to deal with constant encodings
        // Finally don't forget sinceVersion as we might get default values

        // Decode of a constant is simply assignment
        if (token.isConstantEncoding())
        {
            // if primitiveType="char" this is a character array
            if (primitiveType == CHAR)
            {
                if (token.encoding().constValue().size() > 1)
                {
                    // constValue is a string
                    sb.append(String.format(
                        "\tcopy(%1$s[:], \"%2$s\")\n",
                        varName,
                        token.encoding().constValue()));
                }
                else
                {
                    // constValue is a char
                    sb.append(String.format(
                        "\t%1$s[0] = %2$s\n",
                        varName,
                        token.encoding().constValue()));
                }
            }
            else
            {
                sb.append(String.format(
                    "\t%1$s = %2$s\n",
                    varName,
                    generateLiteral(primitiveType, token.encoding().constValue().toString())));
            }
        }
        else
        {
            if (primitiveType == PrimitiveType.CHAR || primitiveType == PrimitiveType.UINT8)
            {
                if (token.arrayLength() > 1)
                {
                    sb.append(String.format(
                        "\tif !%1$sInActingVersion(actingVersion) {\n" +
                        "\t\tfor idx := 0; idx < %2$s; idx++ {\n" +
                        "\t\t\t%1$s[idx] = %1$sNullValue()\n" +
                        "\t\t}\n" +
                        "\t} else {\n" +
                        "\t\tif err := _m.ReadBytes(_r, %1$s[:]); err != nil {\n" +
                        "\t\t\treturn err\n" +
                        "\t\t}\n" +
                        "\t}\n",
                        varName,
                        token.arrayLength()));
                }
                else
                {
                    // A single byte or uint8 gets treated as a uint8
                    sb.append(String.format(
                        "\tif !%1$sInActingVersion(actingVersion) {\n" +
                        "\t\t%1$s = %1$sNullValue()\n" +
                        "\t} else {\n" +
                        "\t\tif err := _m.ReadUint8(_r, &%1$s); err != nil {\n" +
                        "\t\t\treturn err\n" +
                        "\t\t}\n" +
                        "\t}\n",
                        varName));
                }
            }
            else
            {
                if (token.arrayLength() > 1)
                {
                    // Other array types need a for loop
                    sb.append(String.format(
                        "\tif !%2$sInActingVersion(actingVersion) {\n" +
                        "\t\tfor idx := 0; idx < %1$d; idx++ {\n" +
                        "\t\t\t%2$s[idx] = %2$sNullValue()\n" +
                        "\t\t}\n" +
                        "\t} else {\n" +
                        "\t\tfor idx := 0; idx < %1$d; idx++ {\n" +
                        "\t\t\tif err := _m.Read%3$s(_r, &%2$s[idx]); err != nil {\n" +
                        "\t\t\t\treturn err\n" +
                        "\t\t\t}\n" +
                        "\t\t}\n" +
                        "\t}\n",
                        token.arrayLength(),
                        varName,
                        marshalType));
                }
                else
                {
                    sb.append(String.format(
                        "\tif !%1$sInActingVersion(actingVersion) {\n" +
                        "\t\t%1$s = %1$sNullValue()\n" +
                        "\t} else {\n" +
                        "\t\tif err := _m.Read%2$s(_r, &%1$s); err != nil {\n" +
                        "\t\t\treturn err\n" +
                        "\t\t}\n" +
                        "\t}\n",
                        varName,
                        marshalType));
                }
            }
        }
    }

    private void generateRangeCheckPrimitive(
        final StringBuilder sb,
        final String varName,
        final Token token,
        final Boolean isOptional)
    {
        // Note that constant encoding is a property of the type
        // and so works on signal/encoding tokens but optionality is
        // a property of the field and so is not set on the encoding token.
        // For this reason we can use the token to check for constancy
        // but pass in optionality as a parameter

        // Constant values don't need checking
        if (token.isConstantEncoding())
        {
            return;
        }

        // If this field is unknown then we have nothing to check
        // Otherwise do the Min,MaxValue checks (possibly for arrays)
        imports.peek().add("fmt");
        if (token.arrayLength() > 1)
        {
            sb.append(String.format(
                "\tif %1$sInActingVersion(actingVersion) {\n" +
                "\t\tfor idx := 0; idx < %2$s; idx++ {\n" +
                "\t\t\tif %1$s[idx] < %1$sMinValue() || %1$s[idx] > %1$sMaxValue() {\n" +
                "\t\t\t\treturn fmt.Errorf(\"Range check failed on %1$s[%%d] " +
                "(%%v < %%v > %%v)\", idx, %1$sMinValue(), %1$s[idx], %1$sMaxValue())\n" +
                "\t\t\t}\n" +
                "\t\t}\n" +
                "\t}\n",
                varName,
                token.arrayLength()));
        }
        else
        {
            // Optional fields can be NullValue which may be outside the
            // range of Min->Max so we need a special case.
            // Structured this way for go fmt sanity on both cases.
            final String check;
            if (isOptional)
            {
                check = "\t\tif %1$s != %1$sNullValue() && (%1$s < %1$sMinValue() || %1$s > %1$sMaxValue()) {\n";
            }
            else
            {
                check = "\t\tif %1$s < %1$sMinValue() || %1$s > %1$sMaxValue() {\n";
            }

            sb.append(String.format(
                "\tif %1$sInActingVersion(actingVersion) {\n" +
                check +
                "\t\t\treturn fmt.Errorf(\"Range check failed on %1$s " +
                "(%%v < %%v > %%v)\", %1$sMinValue(), %1$s, %1$sMaxValue())\n" +
                "\t\t}\n" +
                "\t}\n",
                varName));
        }

        // Fields that are an [n]byte may have a characterEncoding which
        // should also be checked
        if (token.arrayLength() > 1 && token.encoding().primitiveType() == PrimitiveType.CHAR)
        {
            generateCharacterEncodingRangeCheck(sb, varName, token);
        }
    }

    private void generateOptionalInitPrimitive(
        final StringBuilder sb,
        final String varName,
        final Token token)
    {
        final Encoding encoding = token.encoding();

        // Optional items get initialized to their NullValue
        if (token.arrayLength() > 1)
        {
            sb.append(String.format(
                "\tfor idx := 0; idx < %1$d; idx++ {\n" +
                "\t\t%2$s[idx] = %3$s\n" +
                "\t}\n",
                token.arrayLength(),
                varName,
                generateNullValueLiteral(encoding.primitiveType(), encoding)));
        }
        else
        {
            sb.append(String.format(
                "\t%1$s = %2$s\n",
                varName,
                generateNullValueLiteral(encoding.primitiveType(), encoding)));
        }
    }

    private void generateConstantInitPrimitive(
        final StringBuilder sb,
        final String varName,
        final Token token)
    {
        final Encoding encoding = token.encoding();

        // Decode of a constant is simply assignment
        if (token.isConstantEncoding())
        {
            // if primitiveType="char" this is a character array
            if (encoding.primitiveType() == CHAR)
            {
                if (encoding.constValue().size() > 1)
                {
                    // constValue is a string
                    sb.append(String.format(
                        "\tcopy(%1$s[:], \"%2$s\")\n",
                        varName,
                        encoding.constValue()));
                }
                else
                {
                    // constValue is a char
                    sb.append(String.format(
                        "\t%1$s[0] = %2$s\n",
                        varName,
                        encoding.constValue()));
                }
            }
            else
            {
                sb.append(String.format(
                    "\t%1$s = %2$s\n",
                    varName,
                    generateLiteral(encoding.primitiveType(), encoding.constValue().toString())));
            }
        }
    }

    private void generateEncodeDecodeOpen(
        final StringBuilder encode,
        final StringBuilder decode,
        final StringBuilder rangeCheck,
        final StringBuilder init,
        final char varName,
        final String typeName,
        final Boolean isMessage,
        final Boolean isExtensible)
    {
        generateEncodeHeader(encode, varName, typeName, isMessage, false);
        generateDecodeHeader(decode, varName, typeName, isMessage, isExtensible);
        generateRangeCheckHeader(rangeCheck, varName, typeName, false);
        generateInitHeader(init, varName, typeName);
    }

    private void generateEncodeDecodeClose(
        final StringBuilder encode,
        final StringBuilder decode,
        final StringBuilder rangeCheck,
        final StringBuilder init)
    {
        encode.append("\treturn nil\n}\n");
        decode.append("\treturn nil\n}\n");
        rangeCheck.append("\treturn nil\n}\n");
        init.append("\treturn\n}\n");
    }

    // Newer messages and groups can add extra properties before the variable
    // length elements (groups and varData). We read past the difference
    // between the message's blockLength and our (older) schema's blockLength
    private void generateExtensionCheck(
        final StringBuilder sb,
        final char varName)
    {
        imports.peek().add("io");
        imports.peek().add("io/ioutil");
        sb.append(String.format(
            "\tif actingVersion > %1$s.SbeSchemaVersion() && blockLength > %1$s.SbeBlockLength() {\n" +
            "\t\tio.CopyN(ioutil.Discard, _r, int64(blockLength-%1$s.SbeBlockLength()))\n" +
            "\t}\n",
            varName));
    }

    // Returns the size of the last Message/Group
    private int generateEncodeDecode(
        final StringBuilder sb,
        final String typeName,
        final List tokens,
        final boolean isMessage,
        final boolean isExtensible)
    {
        final char varName = Character.toLowerCase(typeName.charAt(0));
        final StringBuilder encode = new StringBuilder();
        final StringBuilder decode = new StringBuilder();
        final StringBuilder init = new StringBuilder();
        final StringBuilder rangeCheck = new StringBuilder();
        final StringBuilder nested = new StringBuilder();
        int currentOffset = 0;
        int gap;
        boolean extensionStarted = false;

        // Open all our methods
        generateEncodeDecodeOpen(encode, decode, rangeCheck, init, varName, typeName, isMessage, isExtensible);

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

            switch (signalToken.signal())
            {
                case BEGIN_MESSAGE: // Check range *before* we encode setting the acting version to schema version
                    encode.append(String.format(
                        "\tif doRangeCheck {\n" +
                        "\t\tif err := %1$s.RangeCheck(%1$s.SbeSchemaVersion(), %1$s.SbeSchemaVersion());" +
                        " err != nil {\n" +
                        "\t\t\treturn err\n" +
                        "\t\t}\n" +
                        "\t}\n",
                        varName));
                    break;

                case END_MESSAGE:
                    // Newer version extra fields check
                    if (isExtensible && !extensionStarted)
                    {
                        generateExtensionCheck(decode, varName);
                        extensionStarted = true;
                    }

                    // Check range *after* we decode using the acting
                    // version of the encoded message
                    decode.append(String.format(
                        "\tif doRangeCheck {\n" +
                        "\t\tif err := %1$s.RangeCheck(actingVersion, %1$s.SbeSchemaVersion()); err != nil {\n" +
                        "\t\t\treturn err\n" +
                        "\t\t}\n" +
                        "\t}\n",
                        varName));
                    break;

                case BEGIN_ENUM:
                case BEGIN_SET:
                    currentOffset += generatePropertyEncodeDecode(
                        signalToken, typeName, encode, decode, currentOffset);
                    i += signalToken.componentTokenCount() - 1;
                    break;

                case BEGIN_COMPOSITE:
                    currentOffset += generatePropertyEncodeDecode(
                        signalToken, typeName, encode, decode, currentOffset);
                    i += signalToken.componentTokenCount() - 1;

                    rangeCheck.append(String.format(
                        "\tif err := %1$s.%2$s.RangeCheck(actingVersion, schemaVersion); err != nil {\n" +
                        "\t\treturn err\n" +
                        "\t}\n",
                        varName, propertyName));
                    break;

                case BEGIN_FIELD:
                    if (tokens.size() >= i + 1)
                    {
                        currentOffset += generateFieldEncodeDecode(
                            tokens.subList(i, tokens.size() - 1),
                            varName, currentOffset, encode, decode, rangeCheck, init);

                        // Encodings just move past the encoding token
                        if (tokens.get(i + 1).signal() == Signal.ENCODING)
                        {
                            i += 1;
                        }
                        else
                        {
                            i += signalToken.componentTokenCount() - 1;
                        }
                    }
                    break;

                case ENCODING:
                    gap = signalToken.offset() - currentOffset;
                    encode.append(generateEncodeOffset(gap, ""));
                    decode.append(generateDecodeOffset(gap, ""));
                    currentOffset += signalToken.encodedLength() + gap;
                    final String primitive = varName + "." + propertyName;

                    // Encode of a constant is a nullop and we want to
                    // initialize constant values.
                    if (signalToken.isConstantEncoding())
                    {
                        generateConstantInitPrimitive(init, primitive, signalToken);
                    }
                    else
                    {
                        generateEncodePrimitive(encode, varName, formatPropertyName(signalToken.name()), signalToken);
                    }

                    // Optional tokens also get initialized
                    if (signalToken.isOptionalEncoding())
                    {
                        generateOptionalInitPrimitive(init, primitive, signalToken);
                    }

                    generateDecodePrimitive(decode, primitive, signalToken);
                    generateRangeCheckPrimitive(rangeCheck, primitive, signalToken, signalToken.isOptionalEncoding());
                    break;

                case BEGIN_GROUP:
                    // Newer version extra fields check
                    if (isExtensible && !extensionStarted)
                    {
                        generateExtensionCheck(decode, varName);
                        extensionStarted = true;
                    }

                    // Write the group, saving any extra offset we need to skip
                    currentOffset += generateGroupEncodeDecode(
                        tokens.subList(i, tokens.size() - 1),
                        typeName,
                        encode, decode, rangeCheck, currentOffset);

                    // Recurse
                    gap = Math.max(0,
                        signalToken.encodedLength() -
                            generateEncodeDecode(
                                nested,
                                typeName + toUpperFirstChar(signalToken.name()),
                                tokens.subList(i + 5, tokens.size() - 1),
                                false, true));

                    // Group gap block length handling
                    encode.append(generateEncodeOffset(gap, "\t")).append("\t}\n");
                    decode.append(generateDecodeOffset(gap, "\t")).append("\t}\n");

                    // And we can move over this group to the END_GROUP
                    i += signalToken.componentTokenCount() - 1;
                    break;

                case END_GROUP:
                    // Newer version extra fields check
                    if (isExtensible && !extensionStarted)
                    {
                        generateExtensionCheck(decode, varName);
                    }
                    // Close out this group and unwind
                    generateEncodeDecodeClose(encode, decode, rangeCheck, init);
                    sb.append(encode).append(decode).append(rangeCheck).append(init).append(nested);
                    return currentOffset; // for gap calculations

                case BEGIN_VAR_DATA:
                    // Newer version extra fields check
                    if (isExtensible && !extensionStarted)
                    {
                        generateExtensionCheck(decode, varName);
                        extensionStarted = true;
                    }
                    currentOffset += generateVarDataEncodeDecode(
                        tokens.subList(i, tokens.size() - 1),
                        typeName,
                        encode, decode, rangeCheck, currentOffset);
                    // And we can move over this group
                    i += signalToken.componentTokenCount() - 1;
                    break;

                default:
                    break;
            }
        }

        // You can use blockLength on both messages and groups (handled above)
        // to leave some space (akin to an offset).
        final Token endToken = tokens.get(tokens.size() - 1);
        if (endToken.signal() == Signal.END_MESSAGE)
        {
            gap = endToken.encodedLength() - currentOffset;
            encode.append(generateEncodeOffset(gap, ""));
            decode.append(generateDecodeOffset(gap, ""));
        }

        // Close out the methods and append
        generateEncodeDecodeClose(encode, decode, rangeCheck, init);
        sb.append(encode).append(decode).append(rangeCheck).append(init).append(nested);

        return currentOffset;
    }

    private void generateEnumEncodeDecode(final StringBuilder sb, final String enumName, final Token token)
    {
        final char varName = Character.toLowerCase(enumName.charAt(0));
        final String typeName = golangTypeName(token.encoding().primitiveType());
        final String marshalType;

        // The enum type might be char (golang byte) which we encode
        // as a uint8
        if (token.encoding().primitiveType() == PrimitiveType.CHAR)
        {
            marshalType = "Uint8";
        }
        else
        {
            marshalType = golangMarshalType(token.encoding().primitiveType());
        }

        // Encode
        generateEncodeHeader(sb, varName, enumName + "Enum", false, true);
        sb.append(String.format(
            "\tif err := _m.Write%1$s(_w, %2$s(%3$s)); err != nil {\n" +
            "\t\treturn err\n" +
            "\t}\n" +
            "\treturn nil\n}\n",
            marshalType,
            typeName,
            varName));

        // Decode
        generateDecodeHeader(sb, varName, enumName + "Enum", false, false);
        sb.append(String.format(
            "\tif err := _m.Read%1$s(_r, (*%2$s)(%3$s)); err != nil {\n" +
            "\t\treturn err\n" +
            "\t}\n" +
            "\treturn nil\n}\n",
            marshalType,
            typeName,
            varName));

        // Range check
        // We use golang's reflect to range over the values in the
        // struct to check which are legitimate
        imports.peek().add("fmt");
        imports.peek().add("reflect");
        generateRangeCheckHeader(sb, varName, enumName + "Enum", true);

        // For enums we can add new fields so if we're decoding a
        // newer version then the content is definitionally ok.
        // When encoding actingVersion === schemaVersion
        sb.append(
            "\tif actingVersion > schemaVersion {\n" +
            "\t\treturn nil\n" +
            "\t}\n");

        // Otherwise the value should be known
        sb.append(String.format(
            "\tvalue := reflect.ValueOf(%2$s)\n" +
            "\tfor idx := 0; idx < value.NumField(); idx++ {\n" +
            "\t\tif %1$s == value.Field(idx).Interface() {\n" +
            "\t\t\treturn nil\n" +
            "\t\t}\n" +
            "\t}\n" +
            "\treturn fmt.Errorf(\"Range check failed on %2$s, unknown enumeration value %%d\", %1$s)\n" +
            "}\n",
            varName,
            enumName));
    }

    private void generateChoiceEncodeDecode(final StringBuilder sb, final String choiceName, final Token token)
    {
        final char varName = Character.toLowerCase(choiceName.charAt(0));

        generateEncodeHeader(sb, varName, choiceName, false, false);

        sb.append(String.format(
            "\tvar wireval uint%1$d = 0\n" +
            "\tfor k, v := range %2$s {\n" +
            "\t\tif v {\n" +
            "\t\t\twireval |= (1 << uint(k))\n" +
            "\t\t}\n\t}\n" +
            "\treturn _m.WriteUint%1$d(_w, wireval)\n" +
            "}\n",
            token.encodedLength() * 8,
            varName));

        generateDecodeHeader(sb, varName, choiceName, false, false);

        sb.append(String.format(
            "\tvar wireval uint%1$d\n\n" +
            "\tif err := _m.ReadUint%1$d(_r, &wireval); err != nil {\n" +
            "\t\treturn err\n" +
            "\t}\n" +
            "\n" +
            "\tvar idx uint\n" +
            "\tfor idx = 0; idx < %1$d; idx++ {\n" +
            "\t\t%2$s[idx] = (wireval & (1 << idx)) > 0\n" +
            "\t}\n",
            token.encodedLength() * 8,
            varName));

        sb.append("\treturn nil\n}\n");
    }

    private void generateEncodeHeader(
        final StringBuilder sb,
        final char varName,
        final String typeName,
        final Boolean isMessage,
        final Boolean isEnum)
    {
        // Only messages get the rangeCheck flag
        String messageArgs = "";
        if (isMessage)
        {
            messageArgs = ", doRangeCheck bool";
        }

        sb.append(String.format(
            "\nfunc (%1$s %3$s%2$s) Encode(_m *SbeGoMarshaller, _w io.Writer" +
            messageArgs +
            ") error {\n",
            varName,
            typeName,
            (isEnum ? "" : "*")));
    }

    private void generateDecodeHeader(
        final StringBuilder sb,
        final char varName,
        final String typeName,
        final Boolean isMessage,
        final Boolean isExtensible)
    {
        String decodeArgs = "";
        final String blockLengthType = golangTypeName(ir.headerStructure().blockLengthType());

        // Messages, groups, and varData are extensible so need to know
        // working block length.
        // Messages mandate only 16 bits, otherwise let's be generous and
        // support the platform.
        if (isExtensible)
        {
            if (isMessage)
            {
                decodeArgs += ", blockLength " + blockLengthType;
            }
            else
            {
                decodeArgs += ", blockLength uint";
            }
        }

        // Only messages get the rangeCheck flags
        if (isMessage)
        {
            decodeArgs += ", doRangeCheck bool";
        }

        sb.append(String.format(
            "\nfunc (%1$s *%2$s) Decode(_m *SbeGoMarshaller, _r io.Reader, actingVersion uint16" +
            decodeArgs +
            ") error {\n",
            varName,
            typeName));
    }

    private void generateRangeCheckHeader(
        final StringBuilder sb,
        final char varName,
        final String typeName,
        final boolean isEnum)
    {
        sb.append(String.format(
            "\nfunc (%1$s %3$s%2$s) RangeCheck(actingVersion uint16, schemaVersion uint16) error {\n",
            varName,
            typeName,
            (isEnum ? "" : "*")));
    }

    private void generateInitHeader(
        final StringBuilder sb,
        final char varName,
        final String typeName)
    {
        // Init is a function rather than a method to guarantee uniqueness
        // as a field of a structure may collide
        sb.append(String.format(
            "\nfunc %1$sInit(%2$s *%1$s) {\n",
            typeName,
            varName));
    }

    // Returns how many extra tokens to skip over
    private int generateFieldEncodeDecode(
        final List tokens,
        final char varName,
        final int currentOffset,
        final StringBuilder encode,
        final StringBuilder decode,
        final StringBuilder rc,
        final StringBuilder init)
    {
        final Token signalToken = tokens.get(0);
        final Token encodingToken = tokens.get(1);
        final String propertyName = formatPropertyName(signalToken.name());

        int gap = 0; // for offset calculations

        switch (encodingToken.signal())
        {
            case BEGIN_COMPOSITE:
            case BEGIN_ENUM:
            case BEGIN_SET:
                gap = signalToken.offset() - currentOffset;
                encode.append(generateEncodeOffset(gap, ""));
                decode.append(generateDecodeOffset(gap, ""));

                // Encode of a constant is a nullop, decode is assignment
                if (signalToken.isConstantEncoding())
                {
                    decode.append(String.format(
                        "\t%1$s.%2$s = %3$s\n",
                        varName, propertyName, signalToken.encoding().constValue()));
                    init.append(String.format(
                        "\t%1$s.%2$s = %3$s\n",
                        varName, propertyName, signalToken.encoding().constValue()));
                }
                else
                {
                    encode.append(String.format(
                        "\tif err := %1$s.%2$s.Encode(_m, _w); err != nil {\n" +
                        "\t\treturn err\n" +
                        "\t}\n",
                        varName, propertyName));

                    decode.append(String.format(
                        "\tif %1$s.%2$sInActingVersion(actingVersion) {\n" +
                        "\t\tif err := %1$s.%2$s.Decode(_m, _r, actingVersion); err != nil {\n" +
                        "\t\t\treturn err\n" +
                        "\t\t}\n" +
                        "\t}\n",
                        varName, propertyName));
                }

                if (encodingToken.signal() == Signal.BEGIN_ENUM)
                {
                    rc.append(String.format(
                        "\tif err := %1$s.%2$s.RangeCheck(actingVersion, schemaVersion); err != nil {\n" +
                        "\t\treturn err\n" +
                        "\t}\n",
                        varName, propertyName));
                }
                break;

            case ENCODING:
                gap = encodingToken.offset() - currentOffset;
                encode.append(generateEncodeOffset(gap, ""));
                decode.append(generateDecodeOffset(gap, ""));
                final String primitive = varName + "." + propertyName;

                // Encode of a constant is a nullop and we want to
                // initialize constant values.
                // (note: constancy is determined by the type's token)
                if (encodingToken.isConstantEncoding())
                {
                    generateConstantInitPrimitive(init, primitive, encodingToken);
                }
                else
                {
                    generateEncodePrimitive(encode, varName, formatPropertyName(signalToken.name()), encodingToken);
                }

                // Optional tokens get initialized to NullValue
                // (note: optionality is determined by the field's token)
                if (signalToken.isOptionalEncoding())
                {
                    generateOptionalInitPrimitive(init, primitive, encodingToken);
                }

                generateDecodePrimitive(decode, primitive, encodingToken);
                generateRangeCheckPrimitive(rc, primitive, encodingToken, signalToken.isOptionalEncoding());
                break;

            default:
                break;
        }

        return encodingToken.encodedLength() + gap;
    }

    // returns how much to add to offset
    private int generatePropertyEncodeDecode(
        final Token token,
        final String typeName,
        final StringBuilder encode,
        final StringBuilder decode,
        final int currentOffset)
    {
        final char varName = Character.toLowerCase(typeName.charAt(0));
        final String propertyName = formatPropertyName(token.name());
        final int gap = token.offset() - currentOffset;
        encode.append(generateEncodeOffset(gap, ""));
        decode.append(generateDecodeOffset(gap, ""));

        encode.append(String.format(
            "\tif err := %1$s.%2$s.Encode(_m, _w); err != nil {\n" +
            "\t\treturn err\n" +
            "\t}\n",
            varName,
            propertyName));

        decode.append(String.format(
            "\tif %1$s.%2$sInActingVersion(actingVersion) {\n" +
            "\t\tif err := %1$s.%2$s.Decode(_m, _r, actingVersion); err != nil {\n" +
            "\t\t\treturn err\n" +
            "\t\t}\n" +
            "\t}\n",
            varName,
            propertyName));

        return token.encodedLength() + gap;
    }

    // returns how much to add to offset
    private int generateVarDataEncodeDecode(
        final List tokens,
        final String typeName,
        final StringBuilder encode,
        final StringBuilder decode,
        final StringBuilder rc,
        final int currentOffset)
    {
        final Token signalToken = tokens.get(0);
        final char varName = Character.toLowerCase(typeName.charAt(0));
        final String propertyName = formatPropertyName(signalToken.name());

        // Offset handling
        final int gap = Math.max(signalToken.offset() - currentOffset, 0);
        encode.append(generateEncodeOffset(gap, ""));
        decode.append(generateDecodeOffset(gap, ""));

        final String golangTypeForLength = golangTypeName(tokens.get(2).encoding().primitiveType());
        final String golangTypeForLengthMarshal = golangMarshalType(tokens.get(2).encoding().primitiveType());
        final String golangTypeForData = golangTypeName(tokens.get(3).encoding().primitiveType());

        generateCharacterEncodingRangeCheck(rc, varName + "." + propertyName, tokens.get(3));

        encode.append(String.format(
            "\tif err := _m.Write%1$s(_w, %2$s(len(%3$s.%4$s))); err != nil {\n" +
            "\t\treturn err\n" +
            "\t}\n" +
            "\tif err := _m.WriteBytes(_w, %3$s.%4$s); err != nil {\n" +
            "\t\treturn err\n" +
            "\t}\n",
            golangTypeForLengthMarshal,
            golangTypeForLength,
            varName,
            propertyName));

        decode.append(String.format(
            "\n" +
            "\tif %1$c.%2$sInActingVersion(actingVersion) {\n" +
            "\t\tvar %2$sLength %4$s\n" +
            "\t\tif err := _m.Read%3$s(_r, &%2$sLength); err != nil {\n" +
            "\t\t\treturn err\n" +
            "\t\t}\n" +
            "\t\tif cap(%1$c.%2$s) < int(%2$sLength) {\n" +
            "\t\t\t%1$s.%2$s = make([]%5$s, %2$sLength)\n" +
            "\t\t}\n" +
            "\t\t%1$c.%2$s = %1$c.%2$s[:%2$sLength]\n" +
            "\t\tif err := _m.ReadBytes(_r, %1$c.%2$s); err != nil {\n" +
            "\t\t\treturn err\n" +
            "\t\t}\n" +
            "\t}\n",
            varName,
            propertyName,
            golangTypeForLengthMarshal,
            golangTypeForLength,
            golangTypeForData));

        return gap;
    }

    // returns how much to add to offset
    private int generateGroupEncodeDecode(
        final List tokens,
        final String typeName,
        final StringBuilder encode,
        final StringBuilder decode,
        final StringBuilder rc,
        final int currentOffset)
    {
        final char varName = Character.toLowerCase(typeName.charAt(0));
        final Token signalToken = tokens.get(0);
        final String propertyName = formatPropertyName(signalToken.name());
        final Token blockLengthToken = Generators.findFirst("blockLength", tokens, 0);
        final Token numInGroupToken = Generators.findFirst("numInGroup", tokens, 0);
        final int blockLengthOffset = blockLengthToken.offset();
        final String blockLengthType = golangTypeName(blockLengthToken.encoding().primitiveType());
        final String blockLengthMarshalType = golangMarshalType(blockLengthToken.encoding().primitiveType());
        final int numInGroupOffset = numInGroupToken.offset();
        final String numInGroupType = golangTypeName(numInGroupToken.encoding().primitiveType());
        final String numInGroupMarshalType = golangMarshalType(numInGroupToken.encoding().primitiveType());

        // Offset handling
        final int gap = Math.max(signalToken.offset() - currentOffset, 0);
        encode.append(generateEncodeOffset(gap, ""));
        decode.append(generateDecodeOffset(gap, ""));

        final String encBlockLengthTmpl =
            "\tvar %7$sBlockLength %1$s = %2$d\n" +
            "\tif err := _m.Write%6$s(_w, %7$sBlockLength); err != nil {\n" +
            "\t\treturn err\n" +
            "\t}\n";

        final String encNumInGroupTmpl =
            "\tvar %7$sNumInGroup %3$s = %3$s(len(%4$s.%5$s))\n" +
            "\tif err := _m.Write%8$s(_w, %7$sNumInGroup); err != nil {\n" +
            "\t\treturn err\n" +
            "\t}\n";

        // Order write based on offset
        final String encGrpMetaTmpl = blockLengthOffset < numInGroupOffset ?
            encBlockLengthTmpl + encNumInGroupTmpl : encNumInGroupTmpl + encBlockLengthTmpl;

        encode.append(String.format(encGrpMetaTmpl,
            blockLengthType,
            signalToken.encodedLength(),
            numInGroupType,
            varName,
            toUpperFirstChar(signalToken.name()),
            blockLengthMarshalType,
            propertyName,
            numInGroupMarshalType));

        // Write the group itself
        encode.append(String.format(
            "\tfor _, prop := range %1$s.%2$s {\n" +
            "\t\tif err := prop.Encode(_m, _w); err != nil {\n" +
            "\t\t\treturn err\n" +
            "\t\t}\n",
            varName,
            toUpperFirstChar(signalToken.name())));

        decode.append(String.format(
            "\n" +
            "\tif %1$s.%2$sInActingVersion(actingVersion) {\n",
            varName,
            propertyName));

        final String decBlockLengthTmpl =
            "\t\tvar %1$sBlockLength %2$s\n" +
            "\t\tif err := _m.Read%4$s(_r, &%1$sBlockLength); err != nil {\n" +
            "\t\t\treturn err\n" +
            "\t\t}\n";

        final String decNumInGroupTmpl =
            "\t\tvar %1$sNumInGroup %3$s\n" +
            "\t\tif err := _m.Read%5$s(_r, &%1$sNumInGroup); err != nil {\n" +
            "\t\t\treturn err\n" +
            "\t\t}\n";

        final String decGrpMetaTmpl = blockLengthOffset < numInGroupOffset ?
            decBlockLengthTmpl + decNumInGroupTmpl : decNumInGroupTmpl + decBlockLengthTmpl;

        decode.append(String.format(decGrpMetaTmpl,
            propertyName,
            blockLengthType,
            numInGroupType,
            blockLengthMarshalType,
            numInGroupMarshalType));

        // Read the group itself
        decode.append(String.format(
            "\t\tif cap(%1$c.%2$s) < int(%2$sNumInGroup) {\n" +
            "\t\t\t%1$s.%2$s = make([]%3$s%2$s, %2$sNumInGroup)\n" +
            "\t\t}\n" +
            "\t\t%1$c.%2$s = %1$c.%2$s[:%2$sNumInGroup]\n" +
            "\t\tfor i := range %1$s.%2$s {\n" +
            "\t\t\tif err := %1$s.%2$s[i].Decode(_m, _r, actingVersion, uint(%4$sBlockLength)); err != nil {\n" +
            "\t\t\t\treturn err\n" +
            "\t\t\t}\n" +
            "\t\t}\n",
            varName,
            toUpperFirstChar(signalToken.name()),
            typeName,
            propertyName));

        // Range check the group itself
        rc.append(String.format(
            "\tfor _, prop := range %1$s.%2$s {\n" +
            "\t\tif err := prop.RangeCheck(actingVersion, schemaVersion); err != nil {\n" +
            "\t\t\treturn err\n" +
            "\t\t}\n" +
            "\t}\n",
            varName,
            toUpperFirstChar(signalToken.name())));

        return gap;
    }

    // Recursively traverse groups to create the group properties
    private void generateGroupProperties(
        final StringBuilder sb,
        final List tokens,
        final String prefix)
    {
        for (int i = 0, size = tokens.size(); i < size; i++)
        {
            final Token token = tokens.get(i);
            if (token.signal() == Signal.BEGIN_GROUP)
            {
                final String propertyName = formatPropertyName(token.name());

                generateId(sb, prefix, propertyName, token);
                generateSinceActingDeprecated(sb, prefix, propertyName, token);
                generateExtensibilityMethods(sb, prefix + propertyName, token);

                // Look inside for nested groups with extra prefix
                generateGroupProperties(
                    sb,
                    tokens.subList(i + 1, i + token.componentTokenCount() - 1),
                    prefix + propertyName);
                i += token.componentTokenCount() - 1;
            }
        }
    }

    private void generateGroups(final StringBuilder sb, final List tokens, final String prefix)
    {
        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);
            }

            // Make a unique Group name by adding our parent
            final String groupName = prefix + formatTypeName(groupToken.name());

            ++i;
            final int groupHeaderTokenCount = tokens.get(i).componentTokenCount();
            i += groupHeaderTokenCount;

            final List fields = new ArrayList<>();
            i = collectFields(tokens, i, fields);
            generateFields(sb, groupName, fields);

            final List groups = new ArrayList<>();
            i = collectGroups(tokens, i, groups);
            generateGroups(sb, groups, groupName);

            final List varData = new ArrayList<>();
            i = collectVarData(tokens, i, varData);
            generateVarData(sb, formatTypeName(groupName), varData);
        }
    }

    private void generateVarData(
        final StringBuilder sb,
        final String typeName,
        final List tokens)
    {
        for (int i = 0, size = tokens.size(); i < size;)
        {
            final Token token = tokens.get(i);
            if (token.signal() != Signal.BEGIN_VAR_DATA)
            {
                throw new IllegalStateException("tokens must begin with BEGIN_VAR_DATA: token=" + token);
            }

            final String propertyName = toUpperFirstChar(token.name());
            final Token lengthToken = Generators.findFirst("length", tokens, i);
            final int lengthOfLengthField = lengthToken.encodedLength();
            final Token varDataToken = Generators.findFirst("varData", tokens, i);
            final String characterEncoding = varDataToken.encoding().characterEncoding();

            generateFieldMetaAttributeMethod(sb, typeName, propertyName, token);
            generateVarDataDescriptors(sb, token, typeName, propertyName, characterEncoding, lengthOfLengthField);

            i += token.componentTokenCount();
        }
    }

    private void generateVarDataDescriptors(
        final StringBuilder sb,
        final Token token,
        final String typeName,
        final String propertyName,
        final String characterEncoding,
        final Integer lengthOfLengthField)
    {
        generateSinceActingDeprecated(sb, typeName, propertyName, token);
        sb.append(String.format(
            "\nfunc (%1$s) %2$sCharacterEncoding() string {\n" +
            "\treturn \"%3$s\"\n" +
            "}\n" +
            "\nfunc (%1$s) %2$sHeaderLength() uint64 {\n" +
            "\treturn %4$s\n" +
            "}\n",
            typeName,
            propertyName,
            characterEncoding,
            lengthOfLengthField));
    }

    private void generateChoiceSet(final List tokens) throws IOException
    {
        final Token choiceToken = tokens.get(0);
        final String choiceName = formatTypeName(choiceToken.applicableTypeName());
        final StringBuilder sb = new StringBuilder();

        try (Writer out = outputManager.createOutput(choiceName))
        {
            imports.push(new TreeSet<>());
            imports.peek().add("io");

            generateChoiceDecls(
                sb,
                choiceName,
                tokens.subList(1, tokens.size() - 1),
                choiceToken);

            generateChoiceEncodeDecode(sb, choiceName, choiceToken);

            // EncodedLength
            sb.append(String.format(
                "\nfunc (%1$s) EncodedLength() int64 {\n" +
                "\treturn %2$s\n" +
                "}\n",
                choiceName,
                choiceToken.encodedLength()));

            for (final Token token : tokens.subList(1, tokens.size() - 1))
            {
                generateSinceActingDeprecated(sb, choiceName, token.name(), token);
            }
            out.append(generateFileHeader(ir.namespaces()));
            out.append(sb);
            imports.pop();
        }
    }

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

        final StringBuilder sb = new StringBuilder();

        try (Writer out = outputManager.createOutput(enumName))
        {
            imports.push(new TreeSet<>());
            imports.peek().add("io");

            generateEnumDecls(
                sb,
                enumName,
                golangTypeName(tokens.get(0).encoding().primitiveType()),
                tokens.subList(1, tokens.size() - 1),
                enumToken);

            generateEnumEncodeDecode(sb, enumName, enumToken);

            // EncodedLength
            sb.append(String.format(
                "\nfunc (*%1$sEnum) EncodedLength() int64 {\n" +
                "\treturn %2$s\n" +
                "}\n",
                enumName,
                enumToken.encodedLength()));

            for (final Token token : tokens.subList(1, tokens.size() - 1))
            {
                generateSinceActingDeprecated(sb, enumName + "Enum", token.name(), token);
            }

            out.append(generateFileHeader(ir.namespaces()));
            out.append(sb);
            imports.pop();
        }
    }

    private void generateComposite(final List tokens, final String namePrefix) throws IOException
    {
        final String compositeName = namePrefix + formatTypeName(tokens.get(0).applicableTypeName());
        final StringBuilder sb = new StringBuilder();

        try (Writer out = outputManager.createOutput(compositeName))
        {
            imports.push(new TreeSet<>());
            imports.peek().add("io");

            generateTypeDeclaration(sb, compositeName);
            generateTypeBodyComposite(sb, compositeName, tokens.subList(1, tokens.size() - 1));

            generateEncodeDecode(sb, compositeName, tokens.subList(1, tokens.size() - 1), false, false);
            generateEncodedLength(sb, compositeName, tokens.get(0).encodedLength());

            generateCompositePropertyElements(sb, compositeName, tokens.subList(1, tokens.size() - 1));

            // The FileHeader needs to know which imports to add so
            // it's created last once that's known.
            out.append(generateFileHeader(ir.namespaces()));
            out.append(sb);
            imports.pop();
        }
    }

    private void generateEnumDecls(
        final StringBuilder sb,
        final String enumName,
        final String golangType,
        final List tokens,
        final Token encodingToken)
    {
        // gofmt lines up the types and we don't want it to have to rewrite
        // our generated files. To line things up we need to know the longest
        // string length and then fill with whitespace
        final String nullValue = "NullValue";
        int longest = nullValue.length();
        for (final Token token : tokens)
        {
            longest = Math.max(longest, token.name().length());
        }

        // Enums are modelled as a struct and we export an instance so
        // you can reference known values as expected.
        sb.append(String.format(
            "type %1$sEnum %2$s\n" +
            "type %1$sValues struct {\n",
            enumName,
            golangType));

        for (final Token token : tokens)
        {
            sb.append(String.format(
                "\t%1$s%2$s%3$sEnum\n",
                token.name(),
                generateWhitespace(longest - token.name().length() + 1),
                enumName));
        }

        // Add the NullValue
        sb.append(String.format(
            "\t%1$s%2$s%3$sEnum\n" +
            "}\n",
            nullValue,
            generateWhitespace(longest - nullValue.length() + 1),
            enumName));

        // And now the Enum Values expressed as a variable
        sb.append(String.format(
            "\nvar %1$s = %1$sValues{",
            enumName));

        for (final Token token : tokens)
        {
            sb.append(generateLiteral(
                token.encoding().primitiveType(), token.encoding().constValue().toString())).append(", ");
        }

        // Add the NullValue and close
        sb.append(encodingToken.encoding().applicableNullValue().toString()).append("}\n");
    }

    private void generateChoiceDecls(
        final StringBuilder sb,
        final String choiceName,
        final List tokens,
        final Token encodingToken)
    {
        // gofmt lines up the types and we don't want it to have to rewrite
        // our generated files. To line things up we need to know the longest
        // string length and then fill with whitespace
        int longest = 0;
        for (final Token token : tokens)
        {
            longest = Math.max(longest, token.name().length());
        }

        // A ChoiceSet is modelled as an array of bool of size
        // encodedLength in bits (akin to bits in a bitfield).
        // Choice values are modelled as a struct and we export an
        // instance so you can reference known values by name.
        sb.append(String.format(
            "type %1$s [%2$d]bool\n" +
            "type %1$sChoiceValue uint8\n" +
            "type %1$sChoiceValues struct {\n",
            choiceName, encodingToken.encodedLength() * 8));

        for (final Token token : tokens)
        {
            sb.append(String.format(
                "\t%1$s%2$s%3$sChoiceValue\n",
                toUpperFirstChar(token.name()),
                generateWhitespace(longest - token.name().length() + 1),
                toUpperFirstChar(encodingToken.applicableTypeName())));
        }

        sb.append("}\n");

        // And now the Values expressed as a variable
        sb.append(String.format(
            "\nvar %1$sChoice = %1$sChoiceValues{",
            choiceName));

        String comma = "";
        for (final Token token : tokens)
        {
            sb.append(comma)
                .append(generateLiteral(token.encoding().primitiveType(), token.encoding().constValue().toString()));
            comma = ", ";
        }

        sb.append("}\n");
    }

    private String namespacesToPackageName(final CharSequence[] namespaces)
    {
        return String.join("_", namespaces).toLowerCase().replace('.', '_').replace(' ', '_').replace('-', '_');
    }

    private StringBuilder generateFileHeader(final CharSequence[] namespaces)
    {
        final StringBuilder sb = new StringBuilder();
        sb.append("// Generated SBE (Simple Binary Encoding) message codec\n\n");
        sb.append(String.format(
            "package %1$s\n" +
            "\n" +
            "import (\n",
            namespacesToPackageName(namespaces)));

        for (final String s : imports.peek())
        {
            sb.append("\t\"").append(s).append("\"\n");
        }

        sb.append(")\n\n");

        return sb;
    }

    private String generateFromTemplate(final CharSequence[] namespaces, final String templateName)
        throws IOException
    {
        final String templateFileName = "golang/templates/" + templateName + ".go";
        final InputStream stream = getClass().getClassLoader().getResourceAsStream(templateFileName);
        if (null == stream)
        {
            return "";
        }

        try (InputStream in = new BufferedInputStream(stream))
        {
            final Scanner scanner = new Scanner(in).useDelimiter("\\A");
            if (!scanner.hasNext())
            {
                return "";
            }

            return String.format(scanner.next(), namespacesToPackageName(namespaces));
        }
    }

    private static void generateTypeDeclaration(final StringBuilder sb, final String typeName)
    {
        sb.append(String.format("type %s struct {\n", typeName));
    }

    private void generateTypeBody(
        final StringBuilder sb,
        final String typeName,
        final List tokens)
    {
        // gofmt lines up the types and we don't want it to have to rewrite
        // our generated files. To line things up we need to know the longest
        // string length and then fill with whitespace
        int longest = 0;
        for (int i = 0; i < tokens.size(); i++)
        {
            final Token token = tokens.get(i);
            final String propertyName = formatPropertyName(token.name());

            switch (token.signal())
            {
                case BEGIN_GROUP:
                case BEGIN_VAR_DATA:
                    longest = Math.max(longest, propertyName.length());
                    i += token.componentTokenCount() - 1;
                    break;

                case BEGIN_FIELD:
                    longest = Math.max(longest, propertyName.length());
                    break;

                case END_GROUP:
                    i = tokens.size(); // terminate the loop
                    break;

                default:
                    break;
            }
        }

        final StringBuilder nested = new StringBuilder(); // For nested groups
        for (int i = 0; i < tokens.size(); i++)
        {
            final Token signalToken = tokens.get(i);
            final String propertyName = formatPropertyName(signalToken.name());
            final int length = longest - propertyName.length() + 1;

            switch (signalToken.signal())
            {
                case BEGIN_FIELD:
                    if (tokens.size() > i + 1)
                    {
                        final Token encodingToken = tokens.get(i + 1);

                        // it's an array if length > 1, otherwise normally not
                        String arrayspec = "";
                        if (encodingToken.arrayLength() > 1)
                        {
                            arrayspec = "[" + encodingToken.arrayLength() + "]";
                        }

                        switch (encodingToken.signal())
                        {
                            case BEGIN_ENUM:
                                sb.append("\t").append(propertyName)
                                    .append(generateWhitespace(length))
                                    .append(arrayspec)
                                    .append(encodingToken.applicableTypeName())
                                    .append("Enum\n");
                                break;

                            case BEGIN_SET:
                                sb.append("\t").append(propertyName)
                                    .append(generateWhitespace(length))
                                    .append(arrayspec)
                                    .append(encodingToken.applicableTypeName())
                                    .append("\n");
                                break;

                            default:
                                // If the type is primitive then use the golang naming for it
                                String golangType;
                                golangType = golangTypeName(encodingToken.encoding().primitiveType());
                                if (golangType == null)
                                {
                                    golangType = toUpperFirstChar(encodingToken.name());
                                }
                                // If primitiveType="char" and presence="constant"
                                // then this is actually a character array which
                                // can be of length 1
                                if (encodingToken.isConstantEncoding() &&
                                    encodingToken.encoding().primitiveType() == CHAR)
                                {
                                    arrayspec = "[" + encodingToken.encoding().constValue().size() + "]";
                                }
                                sb.append("\t").append(propertyName)
                                    .append(generateWhitespace(length))
                                    .append(arrayspec).append(golangType).append("\n");
                                break;
                        }
                        i++;
                    }
                    break;

                case BEGIN_GROUP:
                    sb.append(String.format(
                        "\t%1$s%2$s[]%3$s%1$s\n",
                        toUpperFirstChar(signalToken.name()),
                        generateWhitespace(length),
                        typeName));
                    generateTypeDeclaration(
                        nested,
                        typeName + toUpperFirstChar(signalToken.name()));
                    generateTypeBody(
                        nested,
                        typeName + toUpperFirstChar(signalToken.name()),
                        tokens.subList(i + 1, tokens.size() - 1));
                    i += signalToken.componentTokenCount() - 1;
                    break;

                case END_GROUP:
                    // Close the group and unwind
                    sb.append("}\n");
                    sb.append(nested);
                    return;

                case BEGIN_VAR_DATA:
                    sb.append(String.format(
                        "\t%1$s%2$s[]%3$s\n",
                        toUpperFirstChar(signalToken.name()),
                        generateWhitespace(length),
                        golangTypeName(tokens.get(i + 3).encoding().primitiveType())));
                    break;

                default:
                    break;
            }
        }
        sb.append("}\n");
        sb.append(nested);
    }

    private void generateCompositePropertyElements(
        final StringBuilder sb,
        final String containingTypeName,
        final List tokens)
    {
        for (int i = 0; i < tokens.size(); )
        {
            final Token token = tokens.get(i);
            final String propertyName = formatPropertyName(token.name());

            // Write {Min,Max,Null}Value
            if (token.signal() == Signal.ENCODING)
            {
                generateMinMaxNull(sb, containingTypeName, propertyName, token);
                generateCharacterEncoding(sb, containingTypeName, propertyName, token);
            }

            switch (token.signal())
            {
                case ENCODING:
                case BEGIN_ENUM:
                case BEGIN_SET:
                case BEGIN_COMPOSITE:
                    generateSinceActingDeprecated(sb, containingTypeName, propertyName, token);
                    break;

                default:
                    break;
            }

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

    private void generateMinMaxNull(
        final StringBuilder sb,
        final String typeName,
        final String propertyName,
        final Token token)
    {
        final Encoding encoding = token.encoding();
        final PrimitiveType primitiveType = encoding.primitiveType();
        final String golangTypeName = golangTypeName(primitiveType);
        final CharSequence nullValueString = generateNullValueLiteral(primitiveType, encoding);
        final CharSequence maxValueString = generateMaxValueLiteral(primitiveType, encoding);
        final CharSequence minValueString = generateMinValueLiteral(primitiveType, encoding);

        // MinValue
        sb.append(String.format(
            "\nfunc (*%1$s) %2$sMinValue() %3$s {\n" +
            "\treturn %4$s\n" +
            "}\n",
            typeName,
            propertyName,
            golangTypeName,
            minValueString));

        // MaxValue
        sb.append(String.format(
            "\nfunc (*%1$s) %2$sMaxValue() %3$s {\n" +
            "\treturn %4$s\n" +
            "}\n",
            typeName,
            propertyName,
            golangTypeName,
            maxValueString));

        // NullValue
        sb.append(String.format(
            "\nfunc (*%1$s) %2$sNullValue() %3$s {\n" +
            "\treturn %4$s\n" +
            "}\n",
            typeName,
            propertyName,
            golangTypeName,
            nullValueString));
    }

    private void generateCharacterEncoding(
        final StringBuilder sb,
        final String typeName,
        final String propertyName,
        final Token token)
    {
        if (token.encoding().primitiveType() == CHAR && token.arrayLength() > 1)
        {
            sb.append(String.format(
                "\nfunc (%1$s *%2$s) %3$sCharacterEncoding() string {\n" +
                "\treturn \"%4$s\"\n" +
                "}\n",
                Character.toLowerCase(typeName.charAt(0)),
                typeName,
                propertyName,
                token.encoding().characterEncoding()));
        }
    }

    private void generateId(
        final StringBuilder sb,
        final String typeName,
        final String propertyName,
        final Token token)
    {
        sb.append(String.format(
            "\nfunc (*%1$s) %2$sId() uint16 {\n" +
            "\treturn %3$s\n" +
            "}\n",
            typeName,
            propertyName,
            token.id()));
    }

    private void generateSinceActingDeprecated(
        final StringBuilder sb,
        final String typeName,
        final String propertyName,
        final Token token)
    {
        sb.append(String.format(
            "\nfunc (*%2$s) %3$sSinceVersion() uint16 {\n" +
            "\treturn %4$s\n" +
            "}\n" +
            "\nfunc (%1$s *%2$s) %3$sInActingVersion(actingVersion uint16) bool {\n" +
            "\treturn actingVersion >= %1$s.%3$sSinceVersion()\n" +
            "}\n" +
            "\nfunc (*%2$s) %3$sDeprecated() uint16 {\n" +
            "\treturn %5$s\n" +
            "}\n",
            Character.toLowerCase(typeName.charAt(0)),
            typeName,
            propertyName,
            token.version(),
            token.deprecated()));
    }

    private void generateTypeBodyComposite(
        final StringBuilder sb,
        final String typeName,
        final List tokens) throws IOException
    {
        // gofmt lines up the types, and we don't want it to have to rewrite
        // our generated files. To line things up we need to know the longest
        // string length and then fill with whitespace
        int longest = 0;
        for (int i = 0; i < tokens.size(); i++)
        {
            final Token token = tokens.get(i);
            final String propertyName = formatPropertyName(token.name());

            switch (token.signal())
            {
                case BEGIN_GROUP:
                case BEGIN_COMPOSITE:
                case BEGIN_VAR_DATA:
                    longest = Math.max(longest, propertyName.length());
                    i += token.componentTokenCount() - 1;
                    break;

                case BEGIN_ENUM:
                case BEGIN_SET:
                case ENCODING:
                    longest = Math.max(longest, propertyName.length());
                    break;

                case END_COMPOSITE:
                    i = tokens.size(); // terminate the loop
                    break;

                default:
                    break;
            }
        }

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

            switch (token.signal())
            {
                case ENCODING:
                    // if a primitiveType="char" and presence="constant" then this is actually a character array
                    if (token.isConstantEncoding() && token.encoding().primitiveType() == CHAR)
                    {
                        arrayLength = token.encoding().constValue().size(); // can be 1
                        sb.append("\t").append(propertyName)
                            .append(generateWhitespace(longest - propertyName.length() + 1))
                            .append("[").append(arrayLength).append("]")
                            .append(golangTypeName(token.encoding().primitiveType())).append("\n");
                    }
                    else
                    {
                        sb.append("\t").append(propertyName)
                            .append(generateWhitespace(longest - propertyName.length() + 1))
                            .append((arrayLength > 1) ? ("[" + arrayLength + "]") : "")
                            .append(golangTypeName(token.encoding().primitiveType())).append("\n");
                    }
                    break;

                case BEGIN_ENUM:
                    sb.append("\t").append(propertyName)
                        .append(generateWhitespace(longest - propertyName.length() + 1))
                        .append((arrayLength > 1) ? ("[" + arrayLength + "]") : "")
                        .append(propertyType).append("Enum\n");
                    break;

                case BEGIN_SET:
                    sb.append("\t").append(propertyName)
                        .append(generateWhitespace(longest - propertyName.length() + 1))
                        .append((arrayLength > 1) ? ("[" + arrayLength + "]") : "").append(propertyType).append("\n");
                    break;

                case BEGIN_COMPOSITE:
                    // recurse
                    generateComposite(tokens.subList(i, i + token.componentTokenCount()), typeName);
                    i += token.componentTokenCount() - 1;

                    sb.append("\t").append(propertyName)
                        .append(generateWhitespace(longest - propertyName.length() + 1))
                        .append((arrayLength > 1) ? ("[" + arrayLength + "]") : "")
                        .append(typeName).append(propertyType).append("\n");
                    break;

                default:
                    break;
            }
        }
        sb.append("}\n");
    }

    private void generateEncodedLength(
        final StringBuilder sb,
        final String typeName,
        final int size)
    {
        sb.append(String.format(
            "\nfunc (*%1$s) EncodedLength() int64 {\n" +
            "\treturn %2$s\n" +
            "}\n",
            typeName,
            size));
    }

    private void generateMessageCode(
        final StringBuilder sb,
        final String typeName,
        final List tokens)
    {
        final Token token = tokens.get(0);
        final String semanticType = token.encoding().semanticType() == null ? "" : token.encoding().semanticType();
        final String blockLengthType = golangTypeName(ir.headerStructure().blockLengthType());
        final String templateIdType = golangTypeName(ir.headerStructure().templateIdType());
        final String schemaIdType = golangTypeName(ir.headerStructure().schemaIdType());
        final String schemaVersionType = golangTypeName(ir.headerStructure().schemaVersionType());
        final String semanticVersion = ir.semanticVersion() == null ? "" : ir.semanticVersion();

        generateEncodeDecode(sb, typeName, tokens, true, true);

        sb.append(String.format(
            "\nfunc (*%1$s) SbeBlockLength() (blockLength %2$s) {\n" +
            "\treturn %3$s\n" +
            "}\n" +
            "\nfunc (*%1$s) SbeTemplateId() (templateId %4$s) {\n" +
            "\treturn %5$s\n" +
            "}\n" +
            "\nfunc (*%1$s) SbeSchemaId() (schemaId %6$s) {\n" +
            "\treturn %7$s\n" +
            "}\n" +
            "\nfunc (*%1$s) SbeSchemaVersion() (schemaVersion %8$s) {\n" +
            "\treturn %9$s\n" +
            "}\n" +
            "\nfunc (*%1$s) SbeSemanticType() (semanticType []byte) {\n" +
            "\treturn []byte(\"%10$s\")\n" +
            "}\n" +
            "\nfunc (*%1$s) SbeSemanticVersion() (semanticVersion string) {\n" +
            "\treturn \"%11$s\"\n" +
            "}\n",
            typeName,
            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,
            semanticVersion));
    }

    // Used for groups which need to know the schema's definition
    // of block length and version to check for extensions
    private void generateExtensibilityMethods(
        final StringBuilder sb,
        final String typeName,
        final Token token)
    {
        sb.append(String.format(
            "\nfunc (*%1$s) SbeBlockLength() (blockLength uint) {\n" +
            "\treturn %2$s\n" +
            "}\n" +
            "\nfunc (*%1$s) SbeSchemaVersion() (schemaVersion %3$s) {\n" +
            "\treturn %4$s\n" +
            "}\n",
            typeName,
            generateLiteral(ir.headerStructure().blockLengthType(), Integer.toString(token.encodedLength())),
            golangTypeName(ir.headerStructure().schemaVersionType()),
            generateLiteral(ir.headerStructure().schemaVersionType(), Integer.toString(ir.version()))));
    }

    private void generateFields(
        final StringBuilder sb,
        final String containingTypeName,
        final List tokens)
    {
        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 = formatPropertyName(signalToken.name());

                generateId(sb, containingTypeName, propertyName, signalToken);
                generateSinceActingDeprecated(sb, containingTypeName, propertyName, signalToken);
                generateFieldMetaAttributeMethod(sb, containingTypeName, propertyName, signalToken);

                if (encodingToken.signal() == Signal.ENCODING)
                {
                    generateMinMaxNull(sb, containingTypeName, propertyName, encodingToken);
                    generateCharacterEncoding(sb, containingTypeName, propertyName, encodingToken);
                }
            }
        }
    }

    private static void generateFieldMetaAttributeMethod(
        final StringBuilder sb,
        final String containingTypeName,
        final String propertyName,
        final Token token)
    {
        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(
            "\nfunc (*%1$s) %2$sMetaAttribute(meta int) string {\n" +
            "\tswitch meta {\n" +
            "\tcase 1:\n" +
            "\t\treturn \"%3$s\"\n" +
            "\tcase 2:\n" +
            "\t\treturn \"%4$s\"\n" +
            "\tcase 3:\n" +
            "\t\treturn \"%5$s\"\n" +
            "\tcase 4:\n" +
            "\t\treturn \"%6$s\"\n" +
            "\t}\n" +
            "\treturn \"\"\n" +
            "}\n",
            containingTypeName,
            propertyName,
            epoch,
            timeUnit,
            semanticType,
            presence));
    }

    private CharSequence generateMinValueLiteral(final PrimitiveType primitiveType, final Encoding encoding)
    {
        if (null == encoding.maxValue())
        {
            switch (primitiveType)
            {
                case CHAR:
                    return "byte(32)";
                case INT8:
                    imports.peek().add("math");
                    return "math.MinInt8 + 1";
                case INT16:
                    imports.peek().add("math");
                    return "math.MinInt16 + 1";
                case INT32:
                    imports.peek().add("math");
                    return "math.MinInt32 + 1";
                case INT64:
                    imports.peek().add("math");
                    return "math.MinInt64 + 1";
                case UINT8:
                case UINT16:
                case UINT32:
                case UINT64:
                    return "0";
                case FLOAT:
                    imports.peek().add("math");
                    return "-math.MaxFloat32";
                case DOUBLE:
                    imports.peek().add("math");
                    return "-math.MaxFloat64";
            }
        }

        return generateLiteral(primitiveType, encoding.applicableMinValue().toString());
    }

    private CharSequence generateMaxValueLiteral(final PrimitiveType primitiveType, final Encoding encoding)
    {
        if (null == encoding.maxValue())
        {
            switch (primitiveType)
            {
                case CHAR:
                    return "byte(126)";
                case INT8:
                    imports.peek().add("math");
                    return "math.MaxInt8";
                case INT16:
                    imports.peek().add("math");
                    return "math.MaxInt16";
                case INT32:
                    imports.peek().add("math");
                    return "math.MaxInt32";
                case INT64:
                    imports.peek().add("math");
                    return "math.MaxInt64";
                case UINT8:
                    imports.peek().add("math");
                    return "math.MaxUint8 - 1";
                case UINT16:
                    imports.peek().add("math");
                    return "math.MaxUint16 - 1";
                case UINT32:
                    imports.peek().add("math");
                    return "math.MaxUint32 - 1";
                case UINT64:
                    imports.peek().add("math");
                    return "math.MaxUint64 - 1";
                case FLOAT:
                    imports.peek().add("math");
                    return "math.MaxFloat32";
                case DOUBLE:
                    imports.peek().add("math");
                    return "math.MaxFloat64";
            }
        }

        return generateLiteral(primitiveType, encoding.applicableMaxValue().toString());
    }

    private CharSequence generateNullValueLiteral(final PrimitiveType primitiveType, final Encoding encoding)
    {
        if (null == encoding.nullValue())
        {
            switch (primitiveType)
            {
                case INT8:
                    imports.peek().add("math");
                    return "math.MinInt8";
                case INT16:
                    imports.peek().add("math");
                    return "math.MinInt16";
                case INT32:
                    imports.peek().add("math");
                    return "math.MinInt32";
                case INT64:
                    imports.peek().add("math");
                    return "math.MinInt64";
                case UINT8:
                    imports.peek().add("math");
                    return "math.MaxUint8";
                case UINT16:
                    imports.peek().add("math");
                    return "math.MaxUint16";
                case UINT32:
                    imports.peek().add("math");
                    return "math.MaxUint32";
                case UINT64:
                    imports.peek().add("math");
                    return "math.MaxUint64";
                default:
                    break;
            }
        }

        return generateLiteral(primitiveType, encoding.applicableNullValue().toString());
    }

    private CharSequence generateLiteral(final PrimitiveType type, final String value)
    {
        String literal = "";

        final String castType = golangTypeName(type);
        switch (type)
        {
            case CHAR:
            case INT8:
            case INT16:
            case INT32:
            case UINT8:
            case UINT16:
            case UINT32:
                literal = value;
                break;

            case UINT64:
                // We get negative numbers from the IR as java has
                // signed types only.
                if (value.charAt(0) == '-')
                {
                    literal = Long.toUnsignedString(Long.parseLong(value));
                }
                else
                {
                    literal = castType + "(" + value + ")";
                }
                break;

            case INT64:
                literal = castType + "(" + value + ")";
                break;

            case FLOAT:
                literal = "float32(" + (value.endsWith("NaN") ? "math.NaN()" : value) + ")";
                break;

            case DOUBLE:
                literal = value.endsWith("NaN") ? "math.NaN()" : value;
                break;
        }

        return literal;
    }

    // Always generates at least one space
    private String generateWhitespace(final int spaces)
    {
        final int limitedSpaces = Math.max(1, spaces);
        return String.format(String.format("%%%ds", limitedSpaces), " ");
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy