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

uk.co.real_logic.sbe.generation.python.PythonGenerator Maven / Gradle / Ivy

/*
 * Copyright 2013 Real Logic Ltd.
 *
 * 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
 *
 * http://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.python;

import uk.co.real_logic.sbe.PrimitiveType;
import uk.co.real_logic.sbe.generation.CodeGenerator;
import uk.co.real_logic.agrona.generation.OutputManager;
import uk.co.real_logic.sbe.ir.*;
import uk.co.real_logic.agrona.Verify;

import java.io.IOException;
import java.io.Writer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;

import static uk.co.real_logic.sbe.generation.python.PythonUtil.*;
import static uk.co.real_logic.sbe.ir.GenerationUtil.collectGroups;
import static uk.co.real_logic.sbe.ir.GenerationUtil.collectRootFields;

public class PythonGenerator implements CodeGenerator
{
    private static final String BASE_INDENT = "";
    private static final String INDENT = "    ";

    private final Ir ir;
    private final OutputManager outputManager;

    public PythonGenerator(final Ir ir, final OutputManager outputManager)
        throws IOException
    {
        Verify.notNull(ir, "ir");
        Verify.notNull(outputManager, "outputManager");

        this.ir = ir;
        this.outputManager = outputManager;
    }

    public void generateMessageHeaderStub() throws IOException
    {
        try (final Writer out = outputManager.createOutput(MESSAGE_HEADER_ENCODER_TYPE))
        {
            final List tokens = ir.headerStructure().tokens();
            out.append(generateFileHeader(ir.applicableNamespace().replace('.', '_'), null));
            out.append(generateClassDeclaration(MESSAGE_HEADER_ENCODER_TYPE));
            out.append(generateFixedFlyweightCode(MESSAGE_HEADER_ENCODER_TYPE, tokens.get(0).encodedLength()));
            out.append(generatePrimitivePropertyEncodings(
                MESSAGE_HEADER_ENCODER_TYPE, tokens.subList(1, tokens.size() - 1), BASE_INDENT));
        }
    }

    public List generateTypeStubs() throws IOException
    {
        final List typesToInclude = new ArrayList<>();

        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;
            }

            typesToInclude.add(tokens.get(0).name());
        }

        return typesToInclude;
    }

    public void generate() throws IOException
    {
        generateMessageHeaderStub();
        final List typesToInclude = generateTypeStubs();

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

            try (final Writer out = outputManager.createOutput(className))
            {
                out.append(generateFileHeader(ir.applicableNamespace().replace('.', '_'), typesToInclude));
                out.append(generateClassDeclaration(className));
                out.append(generateMessageFlyweightCode(msgToken));

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

                final List rootFields = new ArrayList<>();
                offset = collectRootFields(messageBody, offset, rootFields);
                out.append(generateFields(className, rootFields, BASE_INDENT));

                final List groups = new ArrayList<>();
                offset = collectGroups(messageBody, offset, groups);
                final StringBuilder sb = new StringBuilder();
                generateGroups(sb, groups, 0, BASE_INDENT);
                out.append(sb);

                final List varData = messageBody.subList(offset, messageBody.size());
                out.append(generateVarData(varData));
            }
        }
    }

    private int generateGroups(final StringBuilder sb, final List tokens, int index, final String indent)
    {
        for (int size = tokens.size(); index < size; index++)
        {
            if (tokens.get(index).signal() == Signal.BEGIN_GROUP)
            {
                final Token groupToken = tokens.get(index);
                final String groupName = groupToken.name();

                generateGroupClassHeader(sb, groupName, tokens, index, indent + INDENT);

                final List rootFields = new ArrayList<>();
                index = collectRootFields(tokens, ++index, rootFields);
                sb.append(generateFields(groupName, rootFields, indent + INDENT));

                if (tokens.get(index).signal() == Signal.BEGIN_GROUP)
                {
                    index = generateGroups(sb, tokens, index, indent + INDENT);
                }

                sb.append(generateGroupProperty(groupName, groupToken, indent));
            }
        }

        return index;
    }

    private void generateGroupClassHeader(
        final StringBuilder sb, final String groupName, final List tokens, final int index, final String indent)
    {
        final String dimensionsClassName = formatClassName(tokens.get(index + 1).name());
        final int dimensionHeaderSize = tokens.get(index + 1).encodedLength();

        sb.append(String.format(
            "\n" +
            indent + "class %1$s:\n" +
            indent + "    def __init__(self):\n" +
            indent + "        self.buffer_ = 0\n" +
            indent + "        self.bufferLength_ = 0\n" +
            indent + "        self.blockLength_ = 0\n" +
            indent + "        self.count_ = 0\n" +
            indent + "        self.index_ = 0\n" +
            indent + "        self.offset_ = 0\n" +
            indent + "        self.actingVersion_ = 0\n" +
            indent + "        self.position_ = [0]\n" +
            indent + "        self.dimensions_ = %2$s.%2$s()\n\n",
            formatClassName(groupName),
            dimensionsClassName
        ));

        sb.append(String.format(
            indent + "    def wrapForDecode(self, buffer, pos, actingVersion, bufferLength):\n" +
            indent + "        self.buffer_ = buffer\n" +
            indent + "        self.bufferLength_ = bufferLength\n" +
            indent + "        self.dimensions_.wrap(buffer, pos[0], actingVersion, bufferLength)\n" +
            indent + "        self.blockLength_ = self.dimensions_.getBlockLength()\n" +
            indent + "        self.count_ = self.dimensions_.getNumInGroup()\n" +
            indent + "        self.index_ = -1\n" +
            indent + "        self.actingVersion_ = actingVersion\n" +
            indent + "        self.position_ = pos\n" +
            indent + "        self.position_[0] += %1$d\n" +
            indent + "        return self\n\n",
            dimensionHeaderSize
        ));

        final int blockLength = tokens.get(index).encodedLength();
        final String typeForBlockLength = pythonTypeName(
            tokens.get(index + 2).encoding().primitiveType(), tokens.get(index + 2).encoding().byteOrder());
        final String typeForNumInGroup = pythonTypeName(
            tokens.get(index + 3).encoding().primitiveType(), tokens.get(index + 3).encoding().byteOrder());

        sb.append(String.format(
            indent + "    def wrapForEncode(self, buffer, count, pos, actingVersion, bufferLength):\n" +
            indent + "        self.buffer_ = buffer\n" +
            indent + "        self.bufferLength_ = bufferLength\n" +
            indent + "        self.dimensions_.wrap(self.buffer_, pos[0], actingVersion, bufferLength)\n" +
            indent + "        self.dimensions_.setBlockLength(%2$d)\n" +
            indent + "        self.dimensions_.setNumInGroup(count)\n" +
            indent + "        self.index_ = -1\n" +
            indent + "        self.count_ = count\n" +
            indent + "        self.blockLength_ = %2$d\n" +
            indent + "        self.actingVersion_ = actingVersion\n" +
            indent + "        self.position_ = pos\n" +
            indent + "        self.position_[0] += %4$d\n" +
            indent + "        return self\n\n",
            typeForBlockLength,
            blockLength,
            typeForNumInGroup,
            dimensionHeaderSize
        ));

        sb.append(String.format(
            indent + "    @staticmethod\n" +
            indent + "    def sbeHeaderSize():\n" +
            indent + "        return %d\n\n",
            dimensionHeaderSize
        ));

        sb.append(String.format(
            indent + "    @staticmethod\n" +
            indent + "    def sbeBlockLength():\n" +
            indent + "        return %d\n\n",
            blockLength
        ));

        sb.append(String.format(
            indent + "    def count(self):\n" +
            indent + "        return self.count_\n\n" +
            indent + "    def hasNext(self):\n" +
            indent + "        return self.index_ + 1 < self.count_\n\n"
        ));

        sb.append(String.format(
            indent + "    def next(self):\n" +
            indent + "        self.offset_ = self.position_[0]\n" +
            indent + "        if (self.offset_ + self.blockLength_) > self.bufferLength_:\n" +
            indent + "            raise Exception('buffer too short to support next group index')\n" +
            indent + "        self.position_[0] = self.offset_ + self.blockLength_\n" +
            indent + "        self.index_ += 1\n" +
            indent + "        return self\n\n",
            formatClassName(groupName)
        ));
    }

    private CharSequence generateGroupProperty(final String groupName, final Token token, final String indent)
    {
        final StringBuilder sb = new StringBuilder();
        final String className = formatClassName(groupName);
        final String propertyName = formatPropertyName(groupName);

        sb.append(String.format(
            "\n" +
            indent + "    @staticmethod\n" +
            indent + "    def %1$sId():\n" +
            indent + "        return %2$d;\n\n",
            groupName,
            (long)token.id()
        ));

        sb.append(String.format(
            "\n" +
            indent + "    def %2$s(self):\n" +
            indent + "        group = self.%1$s()\n" +
            indent + "        group.wrapForDecode(self.buffer_, self.position_, self.actingVersion_, self.bufferLength_)\n" +
            indent + "        return group\n\n",
            className,
            propertyName
        ));

        sb.append(String.format(
            "\n" +
            indent + "    def %2$sCount(self, count):\n" +
            indent + "        group = self.%1$s()\n" +
            indent + "        group.wrapForEncode(" +
                "self.buffer_, count, self.position_, self.actingVersion_, self.bufferLength_)\n" +
            indent + "        return group\n\n",
            className,
            propertyName
        ));

        return sb;
    }

    private CharSequence generateVarData(final List tokens)
    {
        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)
            {
                final String propertyName = toUpperFirstChar(token.name());
                final String characterEncoding = tokens.get(i + 3).encoding().characterEncoding();
                final Token lengthToken = tokens.get(i + 2);
                final int sizeOfLengthField = lengthToken.encodedLength();
                final String lengthPythonType = pythonTypeName(
                    lengthToken.encoding().primitiveType(), lengthToken.encoding().byteOrder());

                final String byteOrder = lengthToken.encoding().byteOrder() == ByteOrder.BIG_ENDIAN ? ">" : "<";

                generateFieldMetaAttributeMethod(sb, token, BASE_INDENT);

                generateVarDataDescriptors(
                    sb, token, propertyName, characterEncoding, lengthToken, sizeOfLengthField, lengthPythonType);

                sb.append(String.format(
                    "    def get%1$s(self):\n" +
                    "        sizeOfLengthField = %3$d\n" +
                    "        lengthPosition = self.getPosition()\n" +
                    "        dataLength = struct.unpack_from('%5$s', self.buffer_, lengthPosition)[0]\n" +
                    "        self.setPosition(lengthPosition + sizeOfLengthField)\n" +
                    "        pos = self.getPosition()\n" +
                    "        fmt = '" + byteOrder + "' + str(dataLength) + 's'\n" +
                    "        data = struct.unpack_from(fmt, self.buffer_, pos)[0]\n" +
                    "        self.setPosition(pos + dataLength)\n" +
                    "        return data\n\n",
                    propertyName,
                    generateArrayFieldNotPresentCondition(token.version(), BASE_INDENT),
                    sizeOfLengthField,
                    formatByteOrderEncoding(lengthToken.encoding().byteOrder(), lengthToken.encoding().primitiveType()),
                    lengthPythonType
                ));

                sb.append(String.format(
                    "    def set%1$s(self, buffer):\n" +
                    "        sizeOfLengthField = %2$d\n" +
                    "        lengthPosition = self.getPosition()\n" +
                    "        struct.pack_into('%3$s', self.buffer_, lengthPosition, len(buffer))\n" +
                    "        self.setPosition(lengthPosition + sizeOfLengthField)\n" +
                    "        pos = self.getPosition()\n" +
                    "        fmt = '" + byteOrder + "' + str(len(buffer)) + 's'\n" +
                    "        struct.pack_into(fmt, self.buffer_, pos, buffer)\n" +
                    "        self.setPosition(pos + len(buffer))\n\n",
                    propertyName,
                    sizeOfLengthField,
                    lengthPythonType,
                    formatByteOrderEncoding(lengthToken.encoding().byteOrder(), lengthToken.encoding().primitiveType())
                ));
            }
        }

        return sb;
    }

    private void generateVarDataDescriptors(
        final StringBuilder sb,
        final Token token,
        final String propertyName,
        final String characterEncoding,
        final Token lengthToken,
        final Integer sizeOfLengthField,
        final String lengthCpp98Type)
    {
        sb.append(String.format(
            "\n" +
            "    @staticmethod\n" +
            "    def %1$sCharacterEncoding():\n" +
            "        return '%2$s'\n\n",
            formatPropertyName(propertyName),
            characterEncoding
        ));

        sb.append(String.format(
            "    @staticmethod\n" +
            "    def %1$sSinceVersion():\n" +
            "         return %2$d\n\n" +

            "    def %1$sInActingVersion(self):\n" +
            "        return True if self.actingVersion_ >= %2$s else False\n" +

            "    @staticmethod\n" +
            "    def %1$sId():\n" +
            "        return %3$d\n\n",
            formatPropertyName(propertyName),
            (long)token.version(),
            token.id()
        ));

        sb.append(String.format(
            "    @staticmethod\n" +
            "    def %sHeaderSize():\n" +
            "        return %d\n\n",
            toLowerFirstChar(propertyName),
            sizeOfLengthField
        ));

        sb.append(String.format(
            "    def %1$sLength(self):\n" +
            "        return struct.unpack_from('%4$s', self.buffer_, getPosition())[0]\n\n",
            formatPropertyName(propertyName),
            generateArrayFieldNotPresentCondition(token.version(), BASE_INDENT),
            formatByteOrderEncoding(lengthToken.encoding().byteOrder(), lengthToken.encoding().primitiveType()),
            lengthCpp98Type
        ));
    }

    private void generateChoiceSet(final List tokens) throws IOException
    {
        final String bitSetName = formatClassName(tokens.get(0).name());

        try (final Writer out = outputManager.createOutput(bitSetName))
        {
            out.append(generateFileHeader(ir.applicableNamespace().replace('.', '_'), null));
            out.append(generateClassDeclaration(bitSetName));
            out.append(generateFixedFlyweightCode(bitSetName, tokens.get(0).encodedLength()));
            out.append(String.format(
                "\n" +
                "    def clear(self):\n" +
                "        struct.pack_into('%2$s', self.buffer_, self.offset_, 0)\n" +
                "        return self\n\n",
                bitSetName,
                pythonTypeName(tokens.get(0).encoding().primitiveType(), tokens.get(0).encoding().byteOrder())
            ));

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

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

        try (final Writer out = outputManager.createOutput(enumName))
        {
            out.append(generateFileHeader(ir.applicableNamespace().replace('.', '_'), null));
            out.append(generateClassDeclaration(enumName));
            out.append(generateEnumValues(tokens.subList(1, tokens.size() - 1), enumToken));
            out.append(generateEnumMethods(enumName));
        }
    }

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

        try (final Writer out = outputManager.createOutput(compositeName))
        {
            out.append(generateFileHeader(ir.applicableNamespace().replace('.', '_'), null));
            out.append(generateClassDeclaration(compositeName));
            out.append(generateFixedFlyweightCode(compositeName, tokens.get(0).encodedLength()));
            out.append(generatePrimitivePropertyEncodings(compositeName, tokens.subList(1, tokens.size() - 1), BASE_INDENT));
        }
    }

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

        return String.format(
            indent + "        if self.actingVersion_ < %1$d:\n" +
            indent + "            return False\n\n",
            sinceVersion
        );
    }

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

        for (final Token token : tokens)
        {
            if (token.signal() == Signal.CHOICE)
            {
                final String choiceName = token.name();
                final String typeName = pythonTypeName(token.encoding().primitiveType(), token.encoding().byteOrder());
                final String choiceBitPosition = token.encoding().constValue().toString();
                final String byteOrderStr = formatByteOrderEncoding(
                    token.encoding().byteOrder(), token.encoding().primitiveType());

                sb.append(String.format(
                    "\n" +
                    "    def get%1$s(self):\n" +
                    "        return True if struct.unpack_from(" +
                    "'%4$s', self.buffer_, self.offset_)[0] & (0x1L << %5$s) > 0 else False\n\n",
                    toUpperFirstChar(choiceName),
                    generateChoiceNotPresentCondition(token.version(), BASE_INDENT),
                    byteOrderStr,
                    typeName,
                    choiceBitPosition
                ));

                sb.append(String.format(
                    "    def set%2$s(self, value):\n" +
                    "        bits = struct.unpack_from('%3$s', self.buffer_, self.offset_)[0]\n" +
                    "        bits = (bits | ( 0x1L << %5$s)) if value > 0 else (bits & ~(0x1L << %5$s))\n" +
                    "        struct.pack_into('%3$s', self.buffer_, self.offset_, bits)\n" +
                    "        return self\n",
                    bitsetClassName,
                    toUpperFirstChar(choiceName),
                    typeName,
                    byteOrderStr,
                    choiceBitPosition
                ));
            }
        }

        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)
        {
            if (encoding.primitiveType() == PrimitiveType.CHAR)
            {
                final char constVal = (char) token.encoding().constValue().longValue();
                sb.append("    ").append(token.name()).append(" = '").append(constVal).append("'\n");
            }
            else
            {
                final CharSequence constVal = generateLiteral(
                        token.encoding().primitiveType(), token.encoding().constValue().toString());
                sb.append("    ").append(token.name()).append(" = ").append(constVal).append("\n");
            }
        }

        // generate the null value

        if (encoding.primitiveType() == PrimitiveType.CHAR)
        {
            sb.append("    NULL_VALUE = '\\0'");
        }
        else
        {
            sb.append(String.format(
                "    NULL_VALUE = %1$s",
                generateLiteral(encoding.primitiveType(), encoding.applicableNullValue().toString())
            ));
        }
            sb.append("\n\n");

        sb.append("    VALID_VALUES = {\n");
        for (final Token token : tokens)
        {
            sb.append("        ").append(token.name()).append(",\n");
        }
        sb.append("        NULL_VALUE,\n    }\n\n");

        sb.append("    AS_TEXT = {\n");

        for (final Token token : tokens)
        {
            sb.append("        ").append(token.name()).append(" : '").append(token.name()).append("',\n");
        }
        sb.append("        NULL_VALUE : 'NULL_VALUE',\n    }\n\n");

        return sb;
    }

    private CharSequence generateEnumMethods(final String name)
    {
        final StringBuilder sb = new StringBuilder();

        sb.append("    def __init__(self, value):\n");
        sb.append("        self.value = value\n");
        sb.append("        if self.value not in ").append(name).append(".VALID_VALUES:\n");
        sb.append("            raise ValueError('Invalid value for ").append(name).append(": {}'.format(value))\n");
        sb.append("\n");
        sb.append("    def __str__(self):\n");
        sb.append("        return ").append(name).append(".AS_TEXT[self.value]\n");

        return sb;
    }

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

        return String.format(
            indent + "        if self.actingVersion_ < %1$d:\n" +
            indent + "            return %2$s\n\n",
            sinceVersion,
            generateLiteral(encoding.primitiveType(), encoding.applicableNullValue().toString())
        );
    }

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

        return String.format(
            indent + "        if self.actingVersion_ < %1$d:\n" +
            indent + "            return False\n\n",
            sinceVersion
        );
    }

    private CharSequence generateFileHeader(final String namespaceName, final List typesToInclude)
    {
        final StringBuilder sb = new StringBuilder();

        sb.append("#\n# Generated SBE (Simple Binary Encoding) message codec\n#\n");
        sb.append("import struct\n\n");

        if (typesToInclude != null)
        {
            for (final String incName : typesToInclude)
            {
                sb.append(String.format(
                    "import %2$s\n",
                    namespaceName,
                    toUpperFirstChar(incName)
                ));
            }
            sb.append("\n");
        }

        return sb;
    }

    private CharSequence generateClassDeclaration(final String name)
    {
        return "class " + name + ":\n";
    }

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

        for (final Token token : tokens)
        {
            if (token.signal() == Signal.ENCODING)
            {
                sb.append(generatePrimitiveProperty(containingClassName, token.name(), token, indent));
            }
        }

        return sb;
    }

    private CharSequence generatePrimitiveProperty(
        final String containingClassName, final String propertyName, final Token token, final String indent)
    {
        final StringBuilder sb = new StringBuilder();

        sb.append(generatePrimitiveFieldMetaData(propertyName, token, indent));

        if (token.isConstantEncoding())
        {
            sb.append(generateConstPropertyMethods(propertyName, token, indent));
        }
        else
        {
            sb.append(generatePrimitivePropertyMethods(containingClassName, propertyName, token, indent));
        }

        return sb;
    }

    private CharSequence generatePrimitivePropertyMethods(
        final String containingClassName, final String propertyName, final Token token, final String indent)
    {
        final int arrayLength = token.arrayLength();

        if (arrayLength == 1)
        {
            return generateSingleValueProperty(containingClassName, propertyName, token, indent);
        }
        else if (arrayLength > 1)
        {
            return generateArrayProperty(propertyName, token, indent);
        }

        return "";
    }

    private CharSequence generatePrimitiveFieldMetaData(final String propertyName, final Token token, final String indent)
    {
        final StringBuilder sb = new StringBuilder();
        final PrimitiveType primitiveType = token.encoding().primitiveType();
        final String pythonTypeName = pythonTypeName(primitiveType, token.encoding().byteOrder());

        sb.append(String.format(
            "\n" +
            indent + "    @staticmethod\n" +
            indent + "    def %2$sNullValue():\n" +
            indent + "        return %3$s\n",
            pythonTypeName,
            propertyName,
            generateLiteral(primitiveType, token.encoding().applicableNullValue().toString())
        ));

        sb.append(String.format(
            "\n" +
            indent + "    @staticmethod\n" +
            indent + "    def %2$sMinValue():\n" +
            indent + "        return %3$s\n",
            pythonTypeName,
            propertyName,
            generateLiteral(primitiveType, token.encoding().applicableMinValue().toString())
        ));

        sb.append(String.format(
            "\n" +
            indent + "    @staticmethod\n" +
            indent + "    def %2$sMaxValue():\n" +
            indent + "        return %3$s\n",
            pythonTypeName,
            propertyName,
            generateLiteral(primitiveType, token.encoding().applicableMaxValue().toString())
        ));

        return sb;
    }

    private CharSequence generateSingleValueProperty(
        final String containingClassName, final String propertyName, final Token token, final String indent)
    {
        final String pythonTypeName = pythonTypeName(token.encoding().primitiveType(), token.encoding().byteOrder());
        final int offset = token.offset();
        final StringBuilder sb = new StringBuilder();

        sb.append(String.format(
            "\n" +
            indent + "    def get%2$s(self):\n" +
            indent + "        return struct.unpack_from('%1$s', self.buffer_, self.offset_ + %5$d)[0]\n\n",
            pythonTypeName,
            toUpperFirstChar(propertyName),
            generateFieldNotPresentCondition(token.version(), token.encoding(), indent),
            formatByteOrderEncoding(token.encoding().byteOrder(), token.encoding().primitiveType()),
            offset
        ));

        sb.append(String.format(
            indent + "    def set%2$s(self, value):\n" +
            indent + "        struct.pack_into('%3$s', self.buffer_, self.offset_ + %4$d, value)\n" +
            indent + "        return self\n",
            formatClassName(containingClassName),
            toUpperFirstChar(propertyName),
            pythonTypeName,
            offset,
            formatByteOrderEncoding(token.encoding().byteOrder(), token.encoding().primitiveType())
        ));

        return sb;
    }

    private CharSequence generateArrayProperty(
        final String propertyName, final Token token, final String indent)
    {
        final String pythonTypeName = pythonTypeName(token.encoding().primitiveType(), token.encoding().byteOrder());
        final int offset = token.offset();

        final StringBuilder sb = new StringBuilder();

        sb.append(String.format(
            "\n" +
            indent + "    @staticmethod\n" +
            indent + "    def %1$sLength():\n" +
            indent + "        return %2$d\n\n",
            propertyName,
            token.arrayLength()
        ));

        sb.append(String.format(
            indent + "    def get%2$s(self, index):\n" +
            indent + "        if index < 0 or index >= %3$d:\n" +
            indent + "            raise Exception('index out of range for %2$s')\n" +
            indent + "        return struct.unpack_from('%1$s', self.buffer_, self.offset_ + %6$d + (index * %7$d))[0]\n\n",
            pythonTypeName,
            toUpperFirstChar(propertyName),
            token.arrayLength(),
            generateFieldNotPresentCondition(token.version(), token.encoding(), indent),
            formatByteOrderEncoding(token.encoding().byteOrder(), token.encoding().primitiveType()),
            offset,
            token.encoding().primitiveType().size()
        ));

        sb.append(String.format(
            indent + "    def set%2$s(self, index, value):\n" +
            indent + "        if index < 0 or index >= %3$d:\n" +
            indent + "            raise Exception('index out of range for %2$s')\n" +
            indent + "        struct.pack_into('%1$s', self.buffer_, self.offset_ + %4$d + (index * %5$d), value)\n",
            pythonTypeName,
            toUpperFirstChar(propertyName),
            token.arrayLength(),
            offset,
            token.encoding().primitiveType().size(),
            formatByteOrderEncoding(token.encoding().byteOrder(), token.encoding().primitiveType())
        ));

        return sb;
    }

    private CharSequence generateConstPropertyMethods(final String propertyName, final Token token, final String indent)
    {
        final String pythonTypeName = pythonTypeName(token.encoding().primitiveType(), token.encoding().byteOrder());

        if (token.encoding().primitiveType() != PrimitiveType.CHAR)
        {
            return String.format(
                "\n" +
                indent + "    def %2$s(self):\n" +
                indent + "        return %3$s\n",
                pythonTypeName,
                propertyName,
                generateLiteral(token.encoding().primitiveType(), token.encoding().constValue().toString())
            );
        }

        final StringBuilder sb = new StringBuilder();
        final byte[] constantValue = token.encoding().constValue().byteArrayValue(token.encoding().primitiveType());
        final StringBuilder values = new StringBuilder();
        for (final byte b : constantValue)
        {
            values.append(b).append(", ");
        }

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

        sb.append(String.format(
            "\n" +
            indent + "    @staticmethod\n" +
            indent + "    def %1$sLength():\n" +
            indent + "        return %2$d\n\n",
            propertyName,
            constantValue.length
        ));

        sb.append(String.format(
            indent + "    def %2$s(self, index):\n" +
            indent + "        %2$sValues = [%3$s]\n" +
            indent + "        return %2$sValues[index]\n",
            pythonTypeName,
            propertyName,
            values
        ));

        return sb;
    }

    private CharSequence generateFixedFlyweightCode(final String className, final int size)
    {
        return String.format(
            "    def __init__(self):\n" +
            "        self.buffer_ = 0\n" +
            "        self.offset_ = 0\n" +
            "        self.actingVersion_ = 0\n\n" +

            "    def wrap(self, buffer, offset, actingVersion, bufferLength):\n" +
            "        if (offset > (bufferLength - %2$s)):\n" +
            "            raise Exception('buffer too short for flyweight')\n" +
            "        self.buffer_ = buffer\n" +
            "        self.offset_ = offset\n" +
            "        self.actingVersion_ = actingVersion\n" +
            "        return self\n\n" +

            "    @staticmethod\n" +
            "    def encodedLength():\n" +
            "        return %2$s\n",
            className,
            size
        );
    }

    private CharSequence generateMessageFlyweightCode(final Token token)
    {
        final String semanticType = token.encoding().semanticType() == null ? "" : token.encoding().semanticType();

        return String.format(
            "    def __init__(self):\n" +
            "        self.buffer_ = 0\n" +
            "        self.bufferLength_ = 0\n" +
            "        self.offset_ = 0\n" +
            "        self.actingBlockLength_ = 0\n" +
            "        self.actingVersion_ = 0\n" +
            "        self.position_ = [0]\n\n" +

            "    @staticmethod\n" +
            "    def sbeBlockLength():\n" +
            "        return %1$s\n\n" +

            "    @staticmethod\n" +
            "    def sbeTemplateId():\n" +
            "        return %2$s\n\n" +

            "    @staticmethod\n" +
            "    def sbeSchemaId():\n" +
            "        return %3$s\n\n" +

            "    @staticmethod\n" +
            "    def sbeSchemaVersion():\n" +
            "        return %4$s\n\n" +

            "    @staticmethod\n" +
            "    def sbeSemanticType():\n" +
            "        return \"%5$s\"\n\n" +

            "    def offset(self):\n" +
            "        return offset_\n\n" +

            "    def wrapForEncode(self, buffer, offset, bufferLength):\n" +
            "        self.buffer_ = buffer\n" +
            "        self.offset_ = offset\n" +
            "        self.bufferLength_ = bufferLength\n" +
            "        self.actingBlockLength_ = self.sbeBlockLength()\n" +
            "        self.actingVersion_ = self.sbeSchemaVersion()\n" +
            "        self.setPosition(offset + self.actingBlockLength_)\n" +
            "        return self\n\n" +

            "    def wrapForDecode(self, buffer, offset, actingBlockLength, actingVersion, bufferLength):\n" +
            "        self.buffer_ = buffer\n" +
            "        self.offset_ = offset\n" +
            "        self.bufferLength_ = bufferLength\n" +
            "        self.actingBlockLength_ = actingBlockLength\n" +
            "        self.actingVersion_ = actingVersion\n" +
            "        self.setPosition(offset + self.actingBlockLength_)\n" +
            "        return self\n\n" +

            "    def getPosition(self):\n" +
            "        return self.position_[0]\n\n" +

            "    def setPosition(self, position):\n" +
            "        if position > self.bufferLength_:\n" +
            "            raise Exception('buffer too short')\n" +
            "        self.position_[0] = position\n\n" +

            "    def encodedLength(self):\n" +
            "        return self.getPosition() - self.offset_\n\n" +

            "    def buffer(self):\n" +
            "        return self.buffer_\n\n" +

            "    def actingVersion(self):\n" +
            "        return self.actingVersion_;\n",

            generateLiteral(ir.headerStructure().blockLengthType(), Integer.toString(token.encodedLength())),
            generateLiteral(ir.headerStructure().templateIdType(), Integer.toString(token.id())),
            generateLiteral(ir.headerStructure().schemaIdType(), Integer.toString(ir.id())),
            generateLiteral(ir.headerStructure().schemaVersionType(), Integer.toString(token.version())),
            semanticType
        );
    }

    private CharSequence generateFields(final String containingClassName, 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 = formatPropertyName(signalToken.name());

                sb.append(String.format(
                    "\n" +
                    indent + "    @staticmethod\n" +
                    indent + "    def %1$sId():\n" +
                    indent + "        return %2$d\n\n",
                    propertyName,
                    signalToken.id()
                ));

                sb.append(String.format(
                    indent + "    @staticmethod\n" +
                    indent + "    def %1$sSinceVersion():\n" +
                    indent + "         return %2$d\n\n" +
                    indent + "    def %1$sInActingVersion(self):\n" +
                    indent + "        return self.actingVersion_ >= %2$d\n",
                    propertyName,
                    (long)signalToken.version()
                ));

                generateFieldMetaAttributeMethod(sb, signalToken, indent);

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

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

                    case BEGIN_SET:
                        sb.append(generateBitsetProperty(propertyName, encodingToken, indent));
                        break;

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

        return sb;
    }

    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();

        sb.append(String.format(
            "\n" +
            indent + "    @staticmethod\n" +
            indent + "    def %sMetaAttribute(meta):\n" +
            indent + "        return \"???\"\n",
            token.name(),
            epoch,
            timeUnit,
            semanticType
        ));
    }

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

        return String.format(
            indent + "        if self.actingVersion_ < %1$d:\n" +
            indent + "            return %2$s.Value.NULL_VALUE\n",
            sinceVersion,
            enumName
        );
    }

    private CharSequence generateEnumProperty(
        final String containingClassName, final String propertyName, final Token token, final String indent)
    {
        final String enumName = token.name();
        final String typeName = pythonTypeName(token.encoding().primitiveType(), token.encoding().byteOrder());
        final int offset = token.offset();
        final StringBuilder sb = new StringBuilder();

        sb.append(String.format(
            "\n" +
            indent + "    def get%2$s(self):\n" +
            indent + "        return %1$s.%1$s(struct.unpack_from( '%5$s', self.buffer_, self.offset_ + %6$d)[0])\n\n",
            enumName,
            toUpperFirstChar(propertyName),
            generateEnumFieldNotPresentCondition(token.version(), enumName, indent),
            formatByteOrderEncoding(token.encoding().byteOrder(), token.encoding().primitiveType()),
            typeName,
            offset
        ));

        sb.append(String.format(
            indent + "    def set%2$s(self, value):\n" +
            indent + "        struct.pack_into('%4$s', self.buffer_, self.offset_ + %5$d, value)\n" +
            indent + "        return self\n",
            formatClassName(containingClassName),
            toUpperFirstChar(propertyName),
            enumName,
            typeName,
            offset,
            formatByteOrderEncoding(token.encoding().byteOrder(), token.encoding().primitiveType())
        ));

        return sb;
    }

    private Object generateBitsetProperty(final String propertyName, final Token token, final String indent)
    {
        final StringBuilder sb = new StringBuilder();
        final String bitsetName = formatClassName(token.name());
        final int offset = token.offset();

        sb.append(String.format(
            "\n" +
            indent + "    def %2$s(self):\n" +
            indent + "        bitset = %1$s.%1$s()\n" +
            indent + "        bitset.wrap(self.buffer_, self.offset_ + %3$d, self.actingVersion_, self.bufferLength_)\n" +
            indent + "        return bitset;\n",
            bitsetName,
            propertyName,
            offset
        ));

        return sb;
    }

    private Object generateCompositeProperty(final String propertyName, final Token token, final String indent)
    {
        final String compositeName = formatClassName(token.name());
        final int offset = token.offset();
        final StringBuilder sb = new StringBuilder();

        sb.append(String.format(
            "\n" +
            indent + "    def %2$s(self):\n" +
            indent + "        %2$s = %1$s.%1$s()\n" +
            indent + "        %2$s.wrap(self.buffer_, self.offset_ + %3$d, self.actingVersion_, self.bufferLength_)\n" +
            indent + "        return %2$s\n",
            compositeName,
            propertyName,
            offset
        ));

        return sb;
    }

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

        switch (type)
        {
            case CHAR:
            case UINT8:
            case UINT16:
            case INT8:
            case INT16:
                literal = value;
                break;

            case UINT32:
            case INT32:
                literal = value;
                break;

            case FLOAT:
                if (value.endsWith("NaN"))
                {
                    literal = "float('NaN')";
                }
                else
                {
                    literal = value;
                }
                break;

            case INT64:
                literal = value + "L";
                break;

            case UINT64:
                literal = "0x" + Long.toHexString(Long.parseLong(value)) + "L";
                break;

            case DOUBLE:
                if (value.endsWith("NaN"))
                {
                    literal = "double('NaN')";
                }
                else
                {
                    literal = value;
                }
                break;
        }

        return literal;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy