You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by hu...@apache.org on 2022/11/14 21:18:25 UTC

[plc4x] 01/01: feat(plc4py/codegen): Copied the Java codegen structure. Need to update flth files now.

This is an automated email from the ASF dual-hosted git repository.

hutcheb pushed a commit to branch plc4py-codegen
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit 88ecca70aee99eb62722dd89ec7cf1914fd1d984
Author: Ben Hutcheson <be...@gmail.com>
AuthorDate: Mon Nov 14 15:17:27 2022 -0600

    feat(plc4py/codegen): Copied the Java codegen structure. Need to update flth files now.
---
 code-generation/language-python/pom.xml            |  126 ++
 .../language/python/PythonLanguageOutput.java      |  100 ++
 .../python/PythonLanguageTemplateHelper.java       | 1258 ++++++++++++++++++++
 ...x.plugins.codegenerator.language.LanguageOutput |   19 +
 .../python/complex-type-template.python.ftlh       |  977 +++++++++++++++
 .../templates/python/data-io-template.python.ftlh  |  522 ++++++++
 .../templates/python/enum-template.python.ftlh     |  164 +++
 .../src/test/resources/integration-test/pom.xml    |  207 ++++
 .../java/test/readwrite/utils/StaticHelper.java    |   90 ++
 .../src/test/resources/settings.xml                |   53 +
 code-generation/pom.xml                            |    8 +-
 sandbox/plc4py/pom.xml                             |   28 +
 12 files changed, 3551 insertions(+), 1 deletion(-)

diff --git a/code-generation/language-python/pom.xml b/code-generation/language-python/pom.xml
new file mode 100644
index 0000000000..1cc4a29279
--- /dev/null
+++ b/code-generation/language-python/pom.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.plc4x</groupId>
+    <artifactId>plc4x-code-generation</artifactId>
+    <version>0.11.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>plc4x-code-generation-language-python</artifactId>
+
+  <name>Code-Generation: Language: Python</name>
+  <description>Code generation template for generating Python code</description>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-invoker-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>integration-test</id>
+            <goals>
+              <!-- Install the current project artifacts into the maven local repo, so they can be used in the test -->
+              <goal>install</goal>
+              <!-- Execute the maven builds defines in src/test/resources -->
+              <goal>integration-test</goal>
+              <!-- Checks the results of the maven builds -->
+              <goal>verify</goal>
+            </goals>
+            <configuration>
+              <skipInvocation>${skip-code-generation-tests}</skipInvocation>
+              <debug>true</debug>
+              <streamLogsOnFailures>true</streamLogsOnFailures>
+              <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
+              <projectsDirectory>src/test/resources</projectsDirectory>
+              <cloneProjectsTo>${project.build.directory}/integration-tests</cloneProjectsTo>
+              <settingsFile>src/test/resources/settings.xml</settingsFile>
+              <extraArtifacts>
+                <extraArtifact>org.apache.plc4x.plugins:plc4x-code-generation-language-base:${plc4x-code-generation.version}</extraArtifact>
+                <extraArtifact>org.apache.plc4x.plugins:plc4x-maven-plugin:${plc4x-code-generation.version}</extraArtifact>
+                <extraArtifact>org.apache.plc4x.plugins:plc4x-code-generation-protocol-base:${plc4x-code-generation.version}</extraArtifact>
+                <extraArtifact>org.apache.plc4x.plugins:plc4x-code-generation-types-base:${plc4x-code-generation.version}</extraArtifact>
+              </extraArtifacts>
+              <pomIncludes>
+                <pomInclude>*/pom.xml</pomInclude>
+              </pomIncludes>
+              <!-- The goals we will be executing in the test-projects -->
+              <goals>
+                <goal>test</goal>
+              </goals>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <!-- We are using the Freemarker module to generate Java code -->
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-language-base-freemarker</artifactId>
+      <version>0.11.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-protocol-base-mspec</artifactId>
+      <version>0.11.0-SNAPSHOT</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x.plugins</groupId>
+      <artifactId>plc4x-code-generation-types-base</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.freemarker</groupId>
+      <artifactId>freemarker</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-text</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.googlejavaformat</groupId>
+      <artifactId>google-java-format</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-protocol-test</artifactId>
+      <version>0.11.0-SNAPSHOT</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-protocol-test</artifactId>
+      <version>0.11.0-SNAPSHOT</version>
+      <classifier>tests</classifier>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageOutput.java b/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageOutput.java
new file mode 100644
index 0000000000..bb48c9023a
--- /dev/null
+++ b/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageOutput.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.plc4x.language.python;
+
+import com.google.googlejavaformat.java.Formatter;
+import com.google.googlejavaformat.java.FormatterException;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import org.apache.commons.io.FileUtils;
+import org.apache.plc4x.plugins.codegenerator.protocol.freemarker.FreemarkerLanguageOutput;
+import org.apache.plc4x.plugins.codegenerator.protocol.freemarker.FreemarkerLanguageTemplateHelper;
+import org.apache.plc4x.plugins.codegenerator.types.definitions.TypeDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+public class PythonLanguageOutput extends FreemarkerLanguageOutput {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(PythonLanguageOutput.class);
+
+    private final Formatter formatter = new Formatter();
+
+    @Override
+    public String getName() {
+        return "python";
+    }
+
+    @Override
+    public Set<String> supportedOptions() {
+        return Collections.singleton("package");
+    }
+
+    @Override
+    public List<String> supportedOutputFlavors() {
+        return Arrays.asList("read-write", "read-only", "passive");
+    }
+
+    @Override
+    protected List<Template> getSpecTemplates(Configuration freemarkerConfiguration) {
+        return Collections.emptyList();
+    }
+
+    @Override
+    protected List<Template> getComplexTypeTemplates(Configuration freemarkerConfiguration) throws IOException {
+        return List.of(freemarkerConfiguration.getTemplate("templates/python/complex-type-template.python.ftlh"));
+    }
+
+    @Override
+    protected List<Template> getEnumTypeTemplates(Configuration freemarkerConfiguration) throws IOException {
+        return Collections.singletonList(
+            freemarkerConfiguration.getTemplate("templates/python/enum-template.python.ftlh"));
+    }
+
+    @Override
+    protected List<Template> getDataIoTemplates(Configuration freemarkerConfiguration) throws IOException {
+        return Collections.singletonList(
+            freemarkerConfiguration.getTemplate("templates/python/data-io-template.python.ftlh"));
+    }
+
+    @Override
+    protected FreemarkerLanguageTemplateHelper getHelper(TypeDefinition thisType, String protocolName, String flavorName, Map<String, TypeDefinition> types,
+                                                         Map<String, String> options) {
+        return new PythonLanguageTemplateHelper(thisType, protocolName, flavorName, types, options);
+    }
+
+    @Override
+    protected void postProcessTemplateOutput(File outputFile) {
+        try {
+            FileUtils.writeStringToFile(
+                outputFile,
+                formatter.formatSourceAndFixImports(
+                    FileUtils.readFileToString(outputFile, StandardCharsets.UTF_8)
+                ),
+                StandardCharsets.UTF_8
+            );
+        } catch (IOException | FormatterException e) {
+            LOGGER.error("Error formatting {}", outputFile, e);
+        }
+    }
+}
diff --git a/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageTemplateHelper.java b/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageTemplateHelper.java
new file mode 100644
index 0000000000..b5ec041b41
--- /dev/null
+++ b/code-generation/language-python/src/main/java/org/apache/plc4x/language/python/PythonLanguageTemplateHelper.java
@@ -0,0 +1,1258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.plc4x.language.python;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.commons.text.WordUtils;
+import org.apache.plc4x.plugins.codegenerator.language.mspec.model.terms.DefaultStringLiteral;
+import org.apache.plc4x.plugins.codegenerator.protocol.freemarker.BaseFreemarkerLanguageTemplateHelper;
+import org.apache.plc4x.plugins.codegenerator.protocol.freemarker.FreemarkerException;
+import org.apache.plc4x.plugins.codegenerator.protocol.freemarker.Tracer;
+import org.apache.plc4x.plugins.codegenerator.types.definitions.*;
+import org.apache.plc4x.plugins.codegenerator.types.fields.*;
+import org.apache.plc4x.plugins.codegenerator.types.references.*;
+import org.apache.plc4x.plugins.codegenerator.types.terms.*;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.*;
+import java.util.function.Function;
+
+@SuppressWarnings({"unused", "WeakerAccess"})
+public class PythonLanguageTemplateHelper extends BaseFreemarkerLanguageTemplateHelper {
+
+    private final Map<String, String> options;
+
+    public PythonLanguageTemplateHelper(TypeDefinition thisType, String protocolName, String flavorName, Map<String, TypeDefinition> types,
+                                      Map<String, String> options) {
+        super(thisType, protocolName, flavorName, types);
+        this.options = options;
+    }
+
+    public String packageName() {
+        return packageName(protocolName, "python", flavorName);
+    }
+
+    public String packageName(String protocolName, String languageName, String languageFlavorName) {
+        return Optional.ofNullable(options.get("package")).orElseGet(() ->
+            "org.apache.plc4x." + String.join("", languageName.split("-")) + "." +
+                String.join("", protocolName.split("-")) + "." +
+                String.join("", languageFlavorName.split("-")));
+    }
+
+    @Override
+    public String getLanguageTypeNameForField(Field field) {
+        // If the referenced type is a DataIo type, the value is of type PlcValue.
+        if (field.isPropertyField()) {
+            PropertyField propertyField = field.asPropertyField().orElseThrow(IllegalStateException::new);
+            if (propertyField.getType().isComplexTypeReference()) {
+                ComplexTypeReference complexTypeReference = propertyField.getType().asComplexTypeReference().orElseThrow(IllegalStateException::new);
+                final TypeDefinition typeDefinition = getTypeDefinitions().get(complexTypeReference.getName());
+                if (typeDefinition instanceof DataIoTypeDefinition) {
+                    return "PlcValue";
+                }
+            }
+        }
+        return getLanguageTypeNameForTypeReference(((TypedField) field).getType(), !field.isOptionalField());
+    }
+
+    public String getNonPrimitiveLanguageTypeNameForField(TypedField field) {
+        return getLanguageTypeNameForTypeReference(field.getType(), false);
+    }
+
+    public String getLanguageTypeNameForSpecType(TypeReference typeReference) {
+        return getLanguageTypeNameForTypeReference(typeReference, true);
+    }
+
+    @Override
+    public String getLanguageTypeNameForTypeReference(TypeReference typeReference) {
+        return getLanguageTypeNameForTypeReference(typeReference, false);
+    }
+
+    public String getLanguageTypeNameForTypeReference(TypeReference typeReference, boolean allowPrimitive) {
+        Objects.requireNonNull(typeReference);
+        if (typeReference instanceof ArrayTypeReference) {
+            final ArrayTypeReference arrayTypeReference = (ArrayTypeReference) typeReference;
+            if (arrayTypeReference.getElementTypeReference().isByteBased()) {
+                return getLanguageTypeNameForTypeReference(arrayTypeReference.getElementTypeReference(), allowPrimitive) + "[]";
+            } else {
+                return "List<" + getLanguageTypeNameForTypeReference(arrayTypeReference.getElementTypeReference(), false) + ">";
+            }
+        }
+        // DataIo data-types always have properties of type PlcValue
+        if (typeReference.isDataIoTypeReference()) {
+            return "PlcValue";
+        }
+        if (typeReference.isNonSimpleTypeReference()) {
+            return typeReference.asNonSimpleTypeReference().orElseThrow().getName();
+        }
+        SimpleTypeReference simpleTypeReference = (SimpleTypeReference) typeReference;
+        switch (simpleTypeReference.getBaseType()) {
+            case BIT:
+                return allowPrimitive ? boolean.class.getSimpleName() : Boolean.class.getSimpleName();
+            case BYTE:
+                return allowPrimitive ? byte.class.getSimpleName() : Byte.class.getSimpleName();
+            case UINT:
+                IntegerTypeReference unsignedIntegerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 4) {
+                    return allowPrimitive ? byte.class.getSimpleName() : Byte.class.getSimpleName();
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 8) {
+                    return allowPrimitive ? short.class.getSimpleName() : Short.class.getSimpleName();
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 16) {
+                    return allowPrimitive ? int.class.getSimpleName() : Integer.class.getSimpleName();
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 32) {
+                    return allowPrimitive ? long.class.getSimpleName() : Long.class.getSimpleName();
+                }
+                return BigInteger.class.getSimpleName();
+            case INT:
+                IntegerTypeReference integerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                if (integerTypeReference.getSizeInBits() <= 8) {
+                    return allowPrimitive ? byte.class.getSimpleName() : Byte.class.getSimpleName();
+                }
+                if (integerTypeReference.getSizeInBits() <= 16) {
+                    return allowPrimitive ? short.class.getSimpleName() : Short.class.getSimpleName();
+                }
+                if (integerTypeReference.getSizeInBits() <= 32) {
+                    return allowPrimitive ? int.class.getSimpleName() : Integer.class.getSimpleName();
+                }
+                if (integerTypeReference.getSizeInBits() <= 64) {
+                    return allowPrimitive ? long.class.getSimpleName() : Long.class.getSimpleName();
+                }
+                return BigInteger.class.getSimpleName();
+            case FLOAT:
+            case UFLOAT:
+                FloatTypeReference floatTypeReference = (FloatTypeReference) simpleTypeReference;
+                int sizeInBits = floatTypeReference.getSizeInBits();
+                if (sizeInBits <= 32) {
+                    return allowPrimitive ? float.class.getSimpleName() : Float.class.getSimpleName();
+                }
+                if (sizeInBits <= 64) {
+                    return allowPrimitive ? double.class.getSimpleName() : Double.class.getSimpleName();
+                }
+                return BigDecimal.class.getSimpleName();
+            case STRING:
+            case VSTRING:
+                return String.class.getSimpleName();
+            case TIME:
+                return LocalTime.class.getSimpleName();
+            case DATE:
+                return LocalDate.class.getSimpleName();
+            case DATETIME:
+                return LocalDateTime.class.getSimpleName();
+
+        }
+        throw new RuntimeException("Unsupported simple type");
+    }
+
+    public String getPlcValueTypeForTypeReference(TypeReference typeReference) {
+        if (!(typeReference instanceof SimpleTypeReference)) {
+            return "PlcStruct";
+        }
+        SimpleTypeReference simpleTypeReference = (SimpleTypeReference) typeReference;
+        switch (simpleTypeReference.getBaseType()) {
+            case BIT:
+                return "PlcBOOL";
+            case BYTE:
+                return "PlcSINT";
+            case UINT:
+                IntegerTypeReference unsignedIntegerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 4) {
+                    return "PlcUSINT";
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 8) {
+                    return "PlcUINT";
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 16) {
+                    return "PlcUDINT";
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 32) {
+                    return "PlcULINT";
+                }
+            case INT:
+                IntegerTypeReference integerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                if (integerTypeReference.getSizeInBits() <= 8) {
+                    return "PlcSINT";
+                }
+                if (integerTypeReference.getSizeInBits() <= 16) {
+                    return "PlcINT";
+                }
+                if (integerTypeReference.getSizeInBits() <= 32) {
+                    return "PlcDINT";
+                }
+                if (integerTypeReference.getSizeInBits() <= 64) {
+                    return "PlcLINT";
+                }
+
+            case FLOAT:
+            case UFLOAT:
+                FloatTypeReference floatTypeReference = (FloatTypeReference) simpleTypeReference;
+                int sizeInBits = floatTypeReference.getSizeInBits();
+                if (sizeInBits <= 32) {
+                    return "PlcREAL";
+                }
+                if (sizeInBits <= 64) {
+                    return "PlcLREAL";
+                }
+            case STRING:
+            case VSTRING:
+                return "PlcSTRING";
+            case TIME:
+            case DATE:
+            case DATETIME:
+                return "PlcTIME";
+        }
+        throw new RuntimeException("Unsupported simple type");
+    }
+
+    @Override
+    public String getNullValueForTypeReference(TypeReference typeReference) {
+        if (typeReference instanceof SimpleTypeReference) {
+            SimpleTypeReference simpleTypeReference = (SimpleTypeReference) typeReference;
+            switch (simpleTypeReference.getBaseType()) {
+                case BIT:
+                    return "false";
+                case BYTE:
+                    return "0";
+                case UINT:
+                    IntegerTypeReference unsignedIntegerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                    if (unsignedIntegerTypeReference.getSizeInBits() <= 16) {
+                        return "0";
+                    }
+                    if (unsignedIntegerTypeReference.getSizeInBits() <= 32) {
+                        return "0l";
+                    }
+                    return "null";
+                case INT:
+                    IntegerTypeReference integerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                    if (integerTypeReference.getSizeInBits() <= 32) {
+                        return "0";
+                    }
+                    if (integerTypeReference.getSizeInBits() <= 64) {
+                        return "0l";
+                    }
+                    return "null";
+                case FLOAT:
+                    FloatTypeReference floatTypeReference = (FloatTypeReference) simpleTypeReference;
+                    int sizeInBits = floatTypeReference.getSizeInBits();
+                    if (sizeInBits <= 32) {
+                        return "0.0f";
+                    }
+                    if (sizeInBits <= 64) {
+                        return "0.0";
+                    }
+                    return "null";
+                case STRING:
+                case VSTRING:
+                    return "null";
+            }
+            throw new FreemarkerException("Unmapped base-type" + simpleTypeReference.getBaseType());
+        } else {
+            return "null";
+        }
+    }
+
+    public int getNumBits(SimpleTypeReference simpleTypeReference) {
+        switch (simpleTypeReference.getBaseType()) {
+            case BIT:
+                return 1;
+            case BYTE:
+                return 8;
+            case UINT:
+            case INT:
+                IntegerTypeReference integerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                return integerTypeReference.getSizeInBits();
+            case FLOAT:
+                FloatTypeReference floatTypeReference = (FloatTypeReference) simpleTypeReference;
+                return floatTypeReference.getSizeInBits();
+            case STRING:
+                StringTypeReference stringTypeReference = (StringTypeReference) simpleTypeReference;
+                return stringTypeReference.getSizeInBits();
+            case VSTRING:
+                throw new IllegalArgumentException("getSizeInBits doesn't work for 'vstring' fields");
+            default:
+                return 0;
+        }
+    }
+
+    @Deprecated
+    @Override
+    public String getReadBufferReadMethodCall(SimpleTypeReference simpleTypeReference, String valueString, TypedField field) {
+        return "/*TODO: migrate me*/" + getReadBufferReadMethodCall("", simpleTypeReference, valueString, field);
+    }
+
+    @Deprecated
+    public String getReadBufferReadMethodCall(String logicalName, SimpleTypeReference simpleTypeReference, String valueString, TypedField field) {
+        switch (simpleTypeReference.getBaseType()) {
+            case BIT:
+                String bitType = "Bit";
+                return "/*TODO: migrate me*/" + "readBuffer.read" + bitType + "(\"" + logicalName + "\")";
+            case BYTE:
+                String byteType = "Byte";
+                return "/*TODO: migrate me*/" + "readBuffer.read" + byteType + "(\"" + logicalName + "\")";
+            case UINT:
+                String unsignedIntegerType;
+                IntegerTypeReference unsignedIntegerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 4) {
+                    unsignedIntegerType = "UnsignedByte";
+                } else if (unsignedIntegerTypeReference.getSizeInBits() <= 8) {
+                    unsignedIntegerType = "UnsignedShort";
+                } else if (unsignedIntegerTypeReference.getSizeInBits() <= 16) {
+                    unsignedIntegerType = "UnsignedInt";
+                } else if (unsignedIntegerTypeReference.getSizeInBits() <= 32) {
+                    unsignedIntegerType = "UnsignedLong";
+                } else {
+                    unsignedIntegerType = "UnsignedBigInteger";
+                }
+                return "/*TODO: migrate me*/" + "readBuffer.read" + unsignedIntegerType + "(\"" + logicalName + "\", " + simpleTypeReference.getSizeInBits() + ")";
+            case INT:
+                String integerType;
+                if (simpleTypeReference.getSizeInBits() <= 8) {
+                    integerType = "SignedByte";
+                } else if (simpleTypeReference.getSizeInBits() <= 16) {
+                    integerType = "Short";
+                } else if (simpleTypeReference.getSizeInBits() <= 32) {
+                    integerType = "Int";
+                } else if (simpleTypeReference.getSizeInBits() <= 64) {
+                    integerType = "Long";
+                } else {
+                    integerType = "BigInteger";
+                }
+                return "/*TODO: migrate me*/" + "readBuffer.read" + integerType + "(\"" + logicalName + "\", " + simpleTypeReference.getSizeInBits() + ")";
+            case FLOAT:
+                String floatType = (simpleTypeReference.getSizeInBits() <= 32) ? "Float" : "Double";
+                return "/*TODO: migrate me*/" + "readBuffer.read" + floatType + "(\"" + logicalName + "\", " + simpleTypeReference.getSizeInBits() + ")";
+            case STRING:
+            case VSTRING:
+                String stringType = "String";
+                final Term encodingTerm = field.getEncoding().orElse(new DefaultStringLiteral("UTF-8"));
+                if (!(encodingTerm instanceof StringLiteral)) {
+                    throw new RuntimeException("Encoding must be a quoted string value");
+                }
+                String encoding = ((StringLiteral) encodingTerm).getValue();
+                String length = Integer.toString(simpleTypeReference.getSizeInBits());
+                if (simpleTypeReference.getBaseType() == SimpleTypeReference.SimpleBaseType.VSTRING) {
+                    VstringTypeReference vstringTypeReference = (VstringTypeReference) simpleTypeReference;
+                    length = toParseExpression(field, INT_TYPE_REFERENCE, vstringTypeReference.getLengthExpression(), null);
+                }
+                return "/*TODO: migrate me*/" + "readBuffer.read" + stringType + "(\"" + logicalName + "\", " + length + ", \"" +
+                    encoding + "\")";
+        }
+        return "/*TODO: migrate me*/" + "";
+    }
+
+    public String getDataReaderCall(TypeReference typeReference) {
+        return getDataReaderCall(typeReference, "enumForValue");
+    }
+
+    public String getDataReaderCall(TypeReference typeReference, String resolverMethod) {
+        if (typeReference.isEnumTypeReference()) {
+            final String languageTypeName = getLanguageTypeNameForTypeReference(typeReference);
+            final SimpleTypeReference enumBaseTypeReference = getEnumBaseTypeReference(typeReference);
+            return "new DataReaderEnumDefault<>(" + languageTypeName + "::" + resolverMethod + ", " + getDataReaderCall(enumBaseTypeReference) + ")";
+        } else if (typeReference.isArrayTypeReference()) {
+            final ArrayTypeReference arrayTypeReference = typeReference.asArrayTypeReference().orElseThrow();
+            return getDataReaderCall(arrayTypeReference.getElementTypeReference(), resolverMethod);
+        } else if (typeReference.isSimpleTypeReference()) {
+            SimpleTypeReference simpleTypeReference = typeReference.asSimpleTypeReference().orElseThrow(IllegalStateException::new);
+            return getDataReaderCall(simpleTypeReference);
+        } else if (typeReference.isComplexTypeReference()) {
+            StringBuilder paramsString = new StringBuilder();
+            ComplexTypeReference complexTypeReference = typeReference.asComplexTypeReference().orElseThrow(IllegalStateException::new);
+            ComplexTypeDefinition typeDefinition = complexTypeReference.getTypeDefinition();
+            String parserCallString = getLanguageTypeNameForTypeReference(typeReference);
+            // In case of DataIo we actually need to use the type name and not what above returns.
+            // (In this case the mspec type name and the result type name differ)
+            if (typeReference.isDataIoTypeReference()) {
+                parserCallString = typeReference.asDataIoTypeReference().orElseThrow().getName();
+            }
+            if (typeDefinition.isDiscriminatedChildTypeDefinition()) {
+                parserCallString = "(" + getLanguageTypeNameForTypeReference(typeReference) + ") " + typeDefinition.getParentType().orElseThrow().getName();
+            }
+            List<Term> paramTerms = complexTypeReference.getParams().orElse(Collections.emptyList());
+            for (int i = 0; i < paramTerms.size(); i++) {
+                Term paramTerm = paramTerms.get(i);
+                final TypeReference argumentType = getArgumentType(complexTypeReference, i);
+                paramsString
+                    .append(", (")
+                    .append(getLanguageTypeNameForTypeReference(argumentType, true))
+                    .append(") (")
+                    .append(toParseExpression(null, argumentType, paramTerm, null))
+                    .append(")");
+            }
+            return "new DataReaderComplexDefault<>(() -> " + parserCallString + ".staticParse(readBuffer" + paramsString + "), readBuffer)";
+        } else {
+            throw new IllegalStateException("What is this type? " + typeReference);
+        }
+    }
+
+    public String getDataReaderCall(SimpleTypeReference simpleTypeReference) {
+        final int sizeInBits = simpleTypeReference.getSizeInBits();
+        switch (simpleTypeReference.getBaseType()) {
+            case BIT:
+                return "readBoolean(readBuffer)";
+            case BYTE:
+                return "readByte(readBuffer, " + sizeInBits + ")";
+            case UINT:
+                if (sizeInBits <= 4) return "readUnsignedByte(readBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 8) return "readUnsignedShort(readBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 16) return "readUnsignedInt(readBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 32) return "readUnsignedLong(readBuffer, " + sizeInBits + ")";
+                return "readUnsignedBigInteger(readBuffer, " + sizeInBits + ")";
+            case INT:
+                if (sizeInBits <= 8) return "readSignedByte(readBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 16) return "readSignedShort(readBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 32) return "readSignedInt(readBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 64) return "readSignedLong(readBuffer, " + sizeInBits + ")";
+                return "readSignedBigInteger(readBuffer, " + sizeInBits + ")";
+            case FLOAT:
+                if (sizeInBits <= 32) return "readFloat(readBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 64) return "readDouble(readBuffer, " + sizeInBits + ")";
+                return "readBigDecimal(readBuffer, " + sizeInBits + ")";
+            case STRING:
+                return "readString(readBuffer, " + sizeInBits + ")";
+            case VSTRING:
+                VstringTypeReference vstringTypeReference = (VstringTypeReference) simpleTypeReference;
+                return "readString(readBuffer, " + toParseExpression(null, INT_TYPE_REFERENCE, vstringTypeReference.getLengthExpression(), null) + ")";
+            case TIME:
+                return "readTime(readBuffer)";
+            case DATE:
+                return "readDate(readBuffer)";
+            case DATETIME:
+                return "readDateTime(readBuffer)";
+            default:
+                throw new UnsupportedOperationException("Unsupported type " + simpleTypeReference.getBaseType());
+        }
+    }
+
+    public String getDataWriterCall(TypeReference typeReference, String fieldName) {
+        if (typeReference.isSimpleTypeReference()) {
+            SimpleTypeReference simpleTypeReference = typeReference.asSimpleTypeReference().orElseThrow(IllegalStateException::new);
+            return getDataWriterCall(simpleTypeReference);
+        } else if (typeReference.isArrayTypeReference()) {
+            final ArrayTypeReference arrayTypeReference = typeReference.asArrayTypeReference().orElseThrow();
+            return getDataWriterCall(arrayTypeReference.getElementTypeReference(), fieldName);
+        } else if (typeReference.isComplexTypeReference()) {
+            return "new DataWriterComplexDefault<>(writeBuffer)";
+        } else {
+            throw new IllegalStateException("What is this type? " + typeReference);
+        }
+    }
+
+    public String getEnumDataWriterCall(EnumTypeReference typeReference, String fieldName, String attributeName) {
+        if (!typeReference.isEnumTypeReference()) {
+            throw new IllegalArgumentException("this method should only be called for enum types");
+        }
+        final String languageTypeName = getLanguageTypeNameForTypeReference(typeReference);
+        SimpleTypeReference outputTypeReference;
+        if ("value".equals(attributeName)) {
+            outputTypeReference = getEnumBaseTypeReference(typeReference);
+        } else {
+            outputTypeReference = getEnumFieldSimpleTypeReference(typeReference.asNonSimpleTypeReference().orElseThrow(), attributeName);
+        }
+        return "new DataWriterEnumDefault<>(" + languageTypeName + "::get" + StringUtils.capitalize(attributeName) + ", " + languageTypeName + "::name, " + getDataWriterCall(outputTypeReference, fieldName) + ")";
+    }
+
+    public String getDataWriterCall(SimpleTypeReference simpleTypeReference) {
+        final int sizeInBits = simpleTypeReference.getSizeInBits();
+        switch (simpleTypeReference.getBaseType()) {
+            case BIT:
+                return "writeBoolean(writeBuffer)";
+            case BYTE:
+                return "writeByte(writeBuffer, " + sizeInBits + ")";
+            case UINT:
+                if (sizeInBits <= 4) return "writeUnsignedByte(writeBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 8) return "writeUnsignedShort(writeBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 16) return "writeUnsignedInt(writeBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 32) return "writeUnsignedLong(writeBuffer, " + sizeInBits + ")";
+                return "writeUnsignedBigInteger(writeBuffer, " + sizeInBits + ")";
+            case INT:
+                if (sizeInBits <= 8) return "writeSignedByte(writeBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 16) return "writeSignedShort(writeBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 32) return "writeSignedInt(writeBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 64) return "writeSignedLong(writeBuffer, " + sizeInBits + ")";
+                return "writeSignedBigInteger(writeBuffer, " + sizeInBits + ")";
+            case FLOAT:
+                if (sizeInBits <= 32) return "writeFloat(writeBuffer, " + sizeInBits + ")";
+                if (sizeInBits <= 64) return "writeDouble(writeBuffer, " + sizeInBits + ")";
+                return "writeBigDecimal(writeBuffer, " + sizeInBits + ")";
+            case STRING:
+                return "writeString(writeBuffer, " + sizeInBits + ")";
+            case VSTRING:
+                VstringTypeReference vstringTypeReference = (VstringTypeReference) simpleTypeReference;
+                return "writeString(writeBuffer, " + toParseExpression(null, INT_TYPE_REFERENCE, vstringTypeReference.getLengthExpression(), null) + ")";
+            case TIME:
+                return "writeTime(writeBuffer)";
+            case DATE:
+                return "writeDate(writeBuffer)";
+            case DATETIME:
+                return "writeDateTime(writeBuffer)";
+            default:
+                throw new UnsupportedOperationException("Unsupported type " + simpleTypeReference.getBaseType());
+        }
+    }
+
+    @Deprecated
+    @Override
+    public String getWriteBufferWriteMethodCall(SimpleTypeReference simpleTypeReference, String fieldName, TypedField field) {
+        return "/*TODO: migrate me*/" + getWriteBufferWriteMethodCall("", simpleTypeReference, fieldName, field);
+    }
+
+    @Deprecated
+    public String getWriteBufferWriteMethodCall(String logicalName, SimpleTypeReference simpleTypeReference, String fieldName, TypedField field, String... writerArgs) {
+        String writerArgsString = "";
+        if (writerArgs.length > 0) {
+            writerArgsString += ", " + StringUtils.join(writerArgs, ", ");
+        }
+        switch (simpleTypeReference.getBaseType()) {
+            case BIT:
+                return "/*TODO: migrate me*/" + "writeBuffer.writeBit(\"" + logicalName + "\", (boolean) " + fieldName + "" + writerArgsString + ")";
+            case BYTE:
+                ByteTypeReference byteTypeReference = (ByteTypeReference) simpleTypeReference;
+                return "/*TODO: migrate me*/" + "writeBuffer.writeByte(\"" + logicalName + "\", ((Number) " + fieldName + ").byteValue()" + writerArgsString + ")";
+            case UINT:
+                IntegerTypeReference unsignedIntegerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 4) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeUnsignedByte(\"" + logicalName + "\", " + unsignedIntegerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").byteValue()" + writerArgsString + ")";
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 8) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeUnsignedShort(\"" + logicalName + "\", " + unsignedIntegerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").shortValue()" + writerArgsString + ")";
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 16) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeUnsignedInt(\"" + logicalName + "\", " + unsignedIntegerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").intValue()" + writerArgsString + ")";
+                }
+                if (unsignedIntegerTypeReference.getSizeInBits() <= 32) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeUnsignedLong(\"" + logicalName + "\", " + unsignedIntegerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").longValue()" + writerArgsString + ")";
+                }
+                return "/*TODO: migrate me*/" + "writeBuffer.writeUnsignedBigInteger(\"" + logicalName + "\", " + unsignedIntegerTypeReference.getSizeInBits() + ", (BigInteger) " + fieldName + "" + writerArgsString + ")";
+            case INT:
+                IntegerTypeReference integerTypeReference = (IntegerTypeReference) simpleTypeReference;
+                if (integerTypeReference.getSizeInBits() <= 8) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeSignedByte(\"" + logicalName + "\", " + integerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").byteValue()" + writerArgsString + ")";
+                }
+                if (integerTypeReference.getSizeInBits() <= 16) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeShort(\"" + logicalName + "\", " + integerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").shortValue()" + writerArgsString + ")";
+                }
+                if (integerTypeReference.getSizeInBits() <= 32) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeInt(\"" + logicalName + "\", " + integerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").intValue()" + writerArgsString + ")";
+                }
+                if (integerTypeReference.getSizeInBits() <= 64) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeLong(\"" + logicalName + "\", " + integerTypeReference.getSizeInBits() + ", ((Number) " + fieldName + ").longValue()" + writerArgsString + ")";
+                }
+                return "/*TODO: migrate me*/" + "writeBuffer.writeBigInteger(\"" + logicalName + "\", " + integerTypeReference.getSizeInBits() + ", BigInteger.valueOf( " + fieldName + ")" + writerArgsString + ")";
+            case FLOAT:
+            case UFLOAT:
+                FloatTypeReference floatTypeReference = (FloatTypeReference) simpleTypeReference;
+                if (floatTypeReference.getSizeInBits() <= 32) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeFloat(\"" + logicalName + "\", " + floatTypeReference.getSizeInBits() + "," + fieldName + "" + writerArgsString + ")";
+                } else if (floatTypeReference.getSizeInBits() <= 64) {
+                    return "/*TODO: migrate me*/" + "writeBuffer.writeDouble(\"" + logicalName + "\", " + floatTypeReference.getSizeInBits() + "," + fieldName + "" + writerArgsString + ")";
+                } else {
+                    throw new RuntimeException("Unsupported float type");
+                }
+            case STRING:
+            case VSTRING:
+                final Term encodingTerm = field.getEncoding().orElse(new DefaultStringLiteral("UTF-8"));
+                if (!(encodingTerm instanceof StringLiteral)) {
+                    throw new RuntimeException("Encoding must be a quoted string value");
+                }
+                String encoding = ((StringLiteral) encodingTerm).getValue();
+                String length = Integer.toString(simpleTypeReference.getSizeInBits());
+                if (simpleTypeReference.getBaseType() == SimpleTypeReference.SimpleBaseType.VSTRING) {
+                    VstringTypeReference vstringTypeReference = (VstringTypeReference) simpleTypeReference;
+                    length = toSerializationExpression(field, INT_TYPE_REFERENCE, vstringTypeReference.getLengthExpression(), thisType.getParserArguments().orElse(Collections.emptyList()));
+                }
+                return "/*TODO: migrate me*/" + "writeBuffer.writeString(\"" + logicalName + "\", " + length + ", \"" +
+                    encoding + "\", (String) " + fieldName + "" + writerArgsString + ")";
+        }
+        throw new FreemarkerException("Unmapped basetype" + simpleTypeReference.getBaseType());
+    }
+
+    public String getReservedValue(ReservedField reservedField) {
+        final String languageTypeName = getLanguageTypeNameForTypeReference(reservedField.getType(), true);
+        if ("BigInteger".equals(languageTypeName)) {
+            return "BigInteger.valueOf(" + reservedField.getReferenceValue() + ")";
+        } else {
+            return "(" + languageTypeName + ") " + reservedField.getReferenceValue();
+        }
+    }
+
+    /**
+     * @param field           this generally only is needed in order to access field attributes such as encoding etc.
+     * @param resultType      the type the resulting expression should have
+     * @param term            the term representing the expression
+     * @param parserArguments any parser arguments, which could be referenced in expressions (Needed for getting the type)
+     * @return Java code which does the things defined in 'term'
+     */
+    public String toParseExpression(Field field, TypeReference resultType, Term term, List<Argument> parserArguments) {
+        Tracer tracer = Tracer.start("toParseExpression");
+        return tracer + toExpression(field, resultType, term, variableLiteral -> tracer.dive("variableExpressionGenerator") + toVariableParseExpression(field, resultType, variableLiteral, parserArguments));
+    }
+
+    /**
+     * @param field               this generally only is needed in order to access field attributes such as encoding etc.
+     * @param resultType          the type the resulting expression should have
+     * @param term                the term representing the expression
+     * @param serializerArguments any serializer arguments, which could be referenced in expressions (Needed for getting the type)
+     * @return Java code which does the things defined in 'term'
+     */
+    public String toSerializationExpression(Field field, TypeReference resultType, Term term, List<Argument> serializerArguments) {
+        Tracer tracer = Tracer.start("toSerializationExpression");
+        return tracer + toExpression(field, resultType, term, variableLiteral -> tracer.dive("variableExpressionGenerator") + toVariableSerializationExpression(field, resultType, variableLiteral, serializerArguments));
+    }
+
+    private String toExpression(Field field, TypeReference resultType, Term term, Function<VariableLiteral, String> variableExpressionGenerator) {
+        Tracer tracer = Tracer.start("toExpression");
+        if (term == null) {
+            return tracer + "";
+        }
+        if (term instanceof Literal) {
+            return toLiteralTermExpression(field, resultType, (Literal) term, variableExpressionGenerator, tracer);
+        } else if (term instanceof UnaryTerm) {
+            return toUnaryTermExpression(field, resultType, (UnaryTerm) term, variableExpressionGenerator, tracer);
+        } else if (term instanceof BinaryTerm) {
+            return toBinaryTermExpression(field, resultType, (BinaryTerm) term, variableExpressionGenerator, tracer);
+        } else if (term instanceof TernaryTerm) {
+            return toTernaryTermExpression(field, resultType, (TernaryTerm) term, variableExpressionGenerator, tracer);
+        } else {
+            throw new RuntimeException("Unsupported Term type " + term.getClass().getName() + ". Actual type " + resultType);
+        }
+    }
+
+    private String toLiteralTermExpression(Field field, TypeReference resultType, Literal literal, Function<VariableLiteral, String> variableExpressionGenerator, Tracer tracer) {
+        tracer = tracer.dive("literal term instanceOf");
+        if (literal instanceof NullLiteral) {
+            tracer = tracer.dive("null literal instanceOf");
+            return tracer + "null";
+        } else if (literal instanceof BooleanLiteral) {
+            tracer = tracer.dive("boolean literal instanceOf");
+            return tracer + Boolean.toString(((BooleanLiteral) literal).getValue());
+        } else if (literal instanceof NumericLiteral) {
+            tracer = tracer.dive("numeric literal instanceOf");
+            final String numberString = ((NumericLiteral) literal).getNumber().toString();
+            if (resultType.isIntegerTypeReference()) {
+                final IntegerTypeReference integerTypeReference = resultType.asIntegerTypeReference().orElseThrow(RuntimeException::new);
+                if (integerTypeReference.getBaseType() == SimpleTypeReference.SimpleBaseType.UINT && integerTypeReference.getSizeInBits() >= 32) {
+                    tracer = tracer.dive("uint >= 32bit");
+                    return tracer + numberString + "L";
+                } else if (integerTypeReference.getBaseType() == SimpleTypeReference.SimpleBaseType.INT && integerTypeReference.getSizeInBits() > 32) {
+                    tracer = tracer.dive("int > 32bit");
+                    return tracer + numberString + "L";
+                }
+            } else if (resultType.isFloatTypeReference()) {
+                final FloatTypeReference floatTypeReference = resultType.asFloatTypeReference().orElseThrow(RuntimeException::new);
+                if (floatTypeReference.getSizeInBits() <= 32) {
+                    tracer = tracer.dive("float < 32bit");
+                    return tracer + numberString + "F";
+                }
+            }
+            return tracer + numberString;
+        } else if (literal instanceof HexadecimalLiteral) {
+            tracer = tracer.dive("hexadecimal literal instanceOf");
+            final String hexString = ((HexadecimalLiteral) literal).getHexString();
+            if (resultType.isIntegerTypeReference()) {
+                final IntegerTypeReference integerTypeReference = resultType.asIntegerTypeReference().orElseThrow(RuntimeException::new);
+                if (integerTypeReference.getBaseType() == SimpleTypeReference.SimpleBaseType.UINT && integerTypeReference.getSizeInBits() >= 32) {
+                    tracer = tracer.dive("uint >= 32bit");
+                    return tracer + hexString + "L";
+                } else if (integerTypeReference.getBaseType() == SimpleTypeReference.SimpleBaseType.INT && integerTypeReference.getSizeInBits() > 32) {
+                    tracer = tracer.dive("int > 32bit");
+                    return tracer + hexString + "L";
+                }
+            }
+            return tracer + hexString;
+        } else if (literal instanceof StringLiteral) {
+            tracer = tracer.dive("string literal instanceOf");
+            return tracer + "\"" + ((StringLiteral) literal).getValue() + "\"";
+        } else if (literal instanceof VariableLiteral) {
+            tracer = tracer.dive("variable literal instanceOf");
+            VariableLiteral variableLiteral = (VariableLiteral) literal;
+            if ("curPos".equals(((VariableLiteral) literal).getName())) {
+                return "(positionAware.getPos() - startPos)";
+            }
+            // If this literal references an Enum type, then we have to output it differently.
+            if (getTypeDefinitions().get(variableLiteral.getName()) instanceof EnumTypeDefinition) {
+                tracer = tracer.dive("enum definition instanceOf");
+                VariableLiteral enumDefinitionChild = variableLiteral.getChild()
+                    .orElseThrow(() -> new RuntimeException("enum definitions should have childs"));
+                return tracer + variableLiteral.getName() + "." + enumDefinitionChild.getName() +
+                    enumDefinitionChild.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse("");
+            } else {
+                return tracer + variableExpressionGenerator.apply(variableLiteral);
+            }
+        } else {
+            throw new RuntimeException("Unsupported Literal type " + literal.getClass().getName());
+        }
+    }
+
+    private String toUnaryTermExpression(Field field, TypeReference resultType, UnaryTerm unaryTerm, Function<VariableLiteral, String> variableExpressionGenerator, Tracer tracer) {
+        tracer = tracer.dive("unary term instanceOf");
+        Term a = unaryTerm.getA();
+        switch (unaryTerm.getOperation()) {
+            case "!":
+                tracer = tracer.dive("case !");
+                if ((resultType != getAnyTypeReference()) && !resultType.isBooleanTypeReference()) {
+                    throw new IllegalArgumentException("'!(...)' expression requires boolean type. Actual type " + resultType);
+                }
+                return tracer + "!(" + toExpression(field, resultType, a, variableExpressionGenerator) + ")";
+            case "-":
+                tracer = tracer.dive("case -");
+                if ((resultType != getAnyTypeReference()) && !resultType.isIntegerTypeReference() && !resultType.isFloatTypeReference()) {
+                    throw new IllegalArgumentException("'-(...)' expression requires integer or floating-point type. Actual type " + resultType);
+                }
+                return tracer + "-(" + toExpression(field, resultType, a, variableExpressionGenerator) + ")";
+            case "()":
+                tracer = tracer.dive("case ()");
+                return tracer + "(" + toExpression(field, resultType, a, variableExpressionGenerator) + ")";
+            default:
+                throw new RuntimeException("Unsupported unary operation type " + unaryTerm.getOperation() + ". Actual type " + resultType);
+        }
+    }
+
+    private String toBinaryTermExpression(Field field, TypeReference resultType, BinaryTerm binaryTerm, Function<VariableLiteral, String> variableExpressionGenerator, Tracer tracer) {
+        tracer = tracer.dive("binary term instanceOf");
+        Term a = binaryTerm.getA();
+        Term b = binaryTerm.getB();
+        String operation = binaryTerm.getOperation();
+        switch (operation) {
+            case "^": {
+                tracer = tracer.dive(operation);
+                if ((resultType != getAnyTypeReference()) && !resultType.isIntegerTypeReference() && !resultType.isFloatTypeReference()) {
+                    throw new IllegalArgumentException("'A^B' expression requires numeric result type. Actual type " + resultType);
+                }
+                return tracer + "Math.pow((" + toExpression(field, resultType, a, variableExpressionGenerator) + "), (" + toExpression(field, resultType, b, variableExpressionGenerator) + "))";
+            }
+            case "*":
+            case "/":
+            case "%":
+            case "+":
+            case "-": {
+                tracer = tracer.dive(operation);
+                if ((resultType != getAnyTypeReference()) && !resultType.isIntegerTypeReference() && !resultType.isFloatTypeReference()) {
+                    throw new IllegalArgumentException("'A" + operation + "B' expression requires numeric result type. Actual type " + resultType);
+                }
+                return tracer + "(" + toExpression(field, resultType, a, variableExpressionGenerator) + ") " + operation + " (" + toExpression(field, resultType, b, variableExpressionGenerator) + ")";
+            }
+            case ">>":
+            case "<<": {
+                tracer = tracer.dive(operation);
+                return tracer + "(" + toExpression(field, resultType, a, variableExpressionGenerator) + ") " + operation + " (" + toExpression(field, INT_TYPE_REFERENCE, b, variableExpressionGenerator) + ")";
+            }
+            case ">=":
+            case "<=":
+            case ">":
+            case "<":
+            case "==":
+            case "!=":
+                if ((resultType != getAnyTypeReference()) && !resultType.isBooleanTypeReference()) {
+                    throw new IllegalArgumentException("'A" + operation + "B' expression requires boolean result type. Actual type " + resultType);
+                }
+                // TODO: Try to infer the types of the arguments in this case
+                return tracer + "(" + toExpression(field, ANY_TYPE_REFERENCE, a, variableExpressionGenerator) + ") " + operation + " (" + toExpression(field, ANY_TYPE_REFERENCE, b, variableExpressionGenerator) + ")";
+            case "&&":
+            case "||":
+                if ((resultType != getAnyTypeReference()) && !resultType.isBooleanTypeReference()) {
+                    throw new IllegalArgumentException("'A" + operation + "B' expression requires boolean result type. Actual type " + resultType);
+                }
+                return tracer + "(" + toExpression(field, resultType, a, variableExpressionGenerator) + ") " + operation + " (" + toExpression(field, resultType, b, variableExpressionGenerator) + ")";
+            case "&":
+            case "|":
+                if ((resultType != getAnyTypeReference()) && !resultType.isIntegerTypeReference() && !resultType.isByteTypeReference()) {
+                    throw new IllegalArgumentException("'A" + operation + "B' expression requires byte or integer result type. Actual type " + resultType);
+                }
+                return tracer + "(" + toExpression(field, resultType, a, variableExpressionGenerator) + ") " + operation + " (" + toExpression(field, resultType, b, variableExpressionGenerator) + ")";
+            default:
+                throw new IllegalArgumentException("Unsupported ternary operation type " + operation);
+        }
+    }
+
+    private String toTernaryTermExpression(Field field, TypeReference resultType, TernaryTerm ternaryTerm, Function<VariableLiteral, String> variableExpressionGenerator, Tracer tracer) {
+        tracer = tracer.dive("ternary term instanceOf");
+        if ("if".equals(ternaryTerm.getOperation())) {
+            Term a = ternaryTerm.getA();
+            Term b = ternaryTerm.getB();
+            Term c = ternaryTerm.getC();
+            return tracer +
+                "(" +
+                "(" + toExpression(field, BOOL_TYPE_REFERENCE, a, variableExpressionGenerator) + ") ? " +
+                toExpression(field, resultType, b, variableExpressionGenerator) + " : " +
+                toExpression(field, resultType, c, variableExpressionGenerator) + "" +
+                ")";
+        } else {
+            throw new IllegalArgumentException("Unsupported ternary operation type " + ternaryTerm.getOperation() + ". Actual type " + resultType);
+        }
+    }
+
+    public String toVariableEnumAccessExpression(VariableLiteral variableLiteral) {
+        return variableLiteral.getName();
+    }
+
+    private String toVariableParseExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, List<Argument> parserArguments) {
+        Tracer tracer = Tracer.start("toVariableParseExpression");
+        // CAST expressions are special as we need to add a ".class" to the second parameter in Java.
+        if ("CAST".equals(variableLiteral.getName())) {
+            return toCastVariableParseExpression(field, resultType, variableLiteral, parserArguments, tracer);
+        }
+        // Special handling for ByteOrder enums (Built in enums)
+        else if ("BIG_ENDIAN".equals(variableLiteral.getName())) {
+            return "ByteOrder.BIG_ENDIAN";
+        } else if ("LITTLE_ENDIAN".equals(variableLiteral.getName())) {
+            return "ByteOrder.LITTLE_ENDIAN";
+        }
+        // If we're referencing an implicit field, we need to handle that differently.
+        else if (isVariableLiteralImplicitField(variableLiteral)) { // If we are accessing implicit fields, we need to rely on a local variable instead.
+            return toImplicitVariableParseExpression(field, resultType, variableLiteral, tracer);
+        }
+        // Call a static function in the drivers StaticHelper
+        else if ("STATIC_CALL".equals(variableLiteral.getName())) {
+            return toStaticCallParseExpression(field, resultType, variableLiteral, parserArguments, tracer);
+        }
+        // Call a built-in global static function
+        else if (variableLiteral.getName().equals(variableLiteral.getName().toUpperCase())) { // All uppercase names are not fields, but utility methods.
+            return toFunctionCallParseExpression(field, resultType, variableLiteral, parserArguments, tracer);
+        }
+        // The synthetic checksumRawData is a local field and should not be accessed as bean property.
+        boolean isParserArg = "readBuffer".equals(variableLiteral.getName());
+        boolean isTypeArg = "_type".equals(variableLiteral.getName());
+        if (!isParserArg && !isTypeArg && parserArguments != null) {
+            for (Argument serializerArgument : parserArguments) {
+                if (serializerArgument.getName().equals(variableLiteral.getName())) {
+                    isParserArg = true;
+                    break;
+                }
+            }
+        }
+        if (isParserArg) {
+            tracer = tracer.dive("parser arg");
+            return tracer + variableLiteral.getName() + variableLiteral.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse("");
+        } else if (isTypeArg) {
+            tracer = tracer.dive("type arg");
+            String part = variableLiteral.getChild().map(VariableLiteral::getName).orElse("");
+            switch (part) {
+                case "name":
+                    return tracer + "\"" + field.getTypeName() + "\"";
+                case "length":
+                    return tracer + "\"" + ((SimpleTypeReference) field).getSizeInBits() + "\"";
+                case "encoding":
+                    String encoding = ((StringLiteral) field.getEncoding().orElse(new DefaultStringLiteral("UTF-8"))).getValue();
+                    return tracer + "\"" + encoding + "\"";
+                default:
+                    return tracer + "";
+            }
+        } else {
+            String indexAddon = "";
+            if (variableLiteral.getIndex().isPresent()) {
+                indexAddon = ".get(" + variableLiteral.getIndex().orElseThrow() + ")";
+            }
+            return tracer + variableLiteral.getName() + indexAddon + variableLiteral.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse("");
+        }
+    }
+
+    private String toCastVariableParseExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, List<Argument> parserArguments, Tracer tracer) {
+        tracer = tracer.dive("CAST");
+        List<Term> arguments = variableLiteral.getArgs().orElseThrow(() -> new RuntimeException("A Cast expression needs arguments"));
+        if (arguments.size() != 2) {
+            throw new RuntimeException("A CAST expression expects exactly two arguments.");
+        }
+        VariableLiteral firstArgument = arguments.get(0).asLiteral()
+            .orElseThrow(() -> new RuntimeException("First argument should be a literal"))
+            .asVariableLiteral()
+            .orElseThrow(() -> new RuntimeException("First argument should be a Variable literal"));
+        StringLiteral typeArgument = arguments.get(1).asLiteral().orElseThrow(() -> new RuntimeException("Second argument should be a String literal"))
+            .asStringLiteral()
+            .orElseThrow(() -> new RuntimeException("Second argument should be a String literal"));
+        String sb = "CAST" + "(" +
+            toVariableParseExpression(field, ANY_TYPE_REFERENCE, firstArgument, parserArguments) +
+            ", " +
+            typeArgument.getValue() + ".class)";
+        return tracer + sb + variableLiteral.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse("");
+    }
+
+    private String toImplicitVariableParseExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, Tracer tracer) {
+        tracer = tracer.dive("implicit");
+        return tracer + variableLiteral.getName();
+    }
+
+    private String toStaticCallParseExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, List<Argument> parserArguments, Tracer tracer) {
+        tracer = tracer.dive("STATIC_CALL");
+        List<Term> arguments = variableLiteral.getArgs().orElseThrow(() -> new RuntimeException("A STATIC_CALL expression needs arguments"));
+        if (arguments.size() < 1) {
+            throw new RuntimeException("A STATIC_CALL expression expects at least one argument.");
+        }
+        // TODO: make it as static import with a emitImport so if a static call is present a "utils" package must be present in the import
+        StringBuilder sb = new StringBuilder();
+        sb.append(packageName()).append(".utils.StaticHelper.");
+        // Get the class and method name
+        String methodName = arguments.get(0).asLiteral()
+            .orElseThrow(() -> new RuntimeException("First argument should be a literal"))
+            .asStringLiteral()
+            .orElseThrow(() -> new RuntimeException("Expecting the first argument of a 'STATIC_CALL' to be a StringLiteral")).
+            getValue();
+        sb.append(methodName).append("(");
+        for (int i = 1; i < arguments.size(); i++) {
+            Term arg = arguments.get(i);
+            if (i > 1) {
+                sb.append(", ");
+            }
+            sb.append(toParseExpression(field, ANY_TYPE_REFERENCE, arg, parserArguments));
+           /*if (arg instanceof VariableLiteral) {
+                VariableLiteral variableLiteralArg = (VariableLiteral) arg;
+                // "readBuffer" is the default name of the reader argument which is always available.
+                boolean isParserArg = "readBuffer".equals(variableLiteralArg.getName());
+                boolean isTypeArg = "_type".equals(variableLiteralArg.getName());
+                if (!isParserArg && !isTypeArg && parserArguments != null) {
+                    for (Argument parserArgument : parserArguments) {
+                        if (parserArgument.getName().equals(variableLiteralArg.getName())) {
+                            isParserArg = true;
+                            break;
+                        }
+                    }
+                }
+                if (isParserArg) {
+                    sb.append(variableLiteralArg.getName()).append(variableLiteralArg.getChild().map(child -> "." + toVariableExpressionRest(child)).orElse(""));
+                } else if (isTypeArg) {// We have to manually evaluate the type information at code-generation time.
+                    String part = variableLiteralArg.getChild().map(VariableLiteral::getName).orElse("");
+                    switch (part) {
+                        case "name":
+                            sb.append("\"").append(field.getTypeName()).append("\"");
+                            break;
+                        case "length":
+                            sb.append("\"").append(((SimpleTypeReference) field).getSizeInBits()).append("\"");
+                            break;
+                        case "encoding":
+                            String encoding = ((StringLiteral) field.getEncoding().orElse(new DefaultStringLiteral("UTF-8"))).getValue();
+                            sb.append("\"").append(encoding).append("\"");
+                            break;
+                    }
+                } else {
+                    sb.append(toVariableParseExpression(field, variableLiteralArg, null));
+                }
+            } else if (arg instanceof StringLiteral) {
+                sb.append(((StringLiteral) arg).getValue());
+            }*/
+        }
+        sb.append(")");
+        if (variableLiteral.getIndex().isPresent()) {
+            // TODO: If this is a byte typed field, this needs to be an array accessor instead.
+            sb.append(".get(").append(variableLiteral.getIndex().orElseThrow()).append(")");
+        }
+        return tracer + sb.toString();
+    }
+
+    private String toFunctionCallParseExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, List<Argument> parserArguments, Tracer tracer) {
+        tracer = tracer.dive("FunctionCall");
+        StringBuilder sb = new StringBuilder(variableLiteral.getName());
+        if (variableLiteral.getArgs().isPresent()) {
+            sb.append("(");
+            boolean firstArg = true;
+            for (Term arg : variableLiteral.getArgs().get()) {
+                if (!firstArg) {
+                    sb.append(", ");
+                }
+                // TODO: Try to infer the type of the argument ...
+                sb.append(toParseExpression(field, ANY_TYPE_REFERENCE, arg, parserArguments));
+                firstArg = false;
+            }
+            sb.append(")");
+        }
+        if (variableLiteral.getIndex().isPresent()) {
+            // TODO: If this is a byte typed field, this needs to be an array accessor instead.
+            sb.append(".get(").append(variableLiteral.getIndex().orElseThrow()).append(")");
+        }
+        return tracer + sb.toString() + variableLiteral.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse("");
+    }
+
+    private String toVariableSerializationExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, List<Argument> serialzerArguments) {
+        Tracer tracer = Tracer.start("variable serialization expression");
+        if ("STATIC_CALL".equals(variableLiteral.getName())) {
+            return toStaticCallSerializationExpression(field, resultType, variableLiteral, serialzerArguments, tracer);
+        }
+        // All uppercase names are not fields, but utility methods.
+        else if (variableLiteral.getName().equals(variableLiteral.getName().toUpperCase())) {
+            return toGlobalFunctionCallSerializationExpression(field, resultType, variableLiteral, serialzerArguments, tracer);
+        } else if (isVariableLiteralImplicitField(variableLiteral)) { // If we are accessing implicit fields, we need to rely on a local variable instead.
+            tracer = tracer.dive("implicit field");
+            final ImplicitField referencedImplicitField = getReferencedImplicitField(variableLiteral);
+            return tracer + toSerializationExpression(referencedImplicitField, referencedImplicitField.getType(), getReferencedImplicitField(variableLiteral).getSerializeExpression(), serialzerArguments);
+        } else if (isVariableLiteralVirtualField(variableLiteral)) {
+            tracer = tracer.dive("virtual field");
+            return tracer + toVariableExpressionRest(field, resultType, variableLiteral);
+        }
+        // The synthetic checksumRawData is a local field and should not be accessed as bean property.
+        boolean isSerializerArg = "writeBuffer".equals(variableLiteral.getName()) || "checksumRawData".equals(variableLiteral.getName()) || "_value".equals(variableLiteral.getName()) || "element".equals(variableLiteral.getName()) || "size".equals(variableLiteral.getName());
+        boolean isTypeArg = "_type".equals(variableLiteral.getName());
+        if (!isSerializerArg && !isTypeArg && serialzerArguments != null) {
+            for (Argument serializerArgument : serialzerArguments) {
+                if (serializerArgument.getName().equals(variableLiteral.getName())) {
+                    isSerializerArg = true;
+                    break;
+                }
+            }
+        }
+        if (isSerializerArg) {
+            tracer = tracer.dive("serializer arg");
+            return tracer + variableLiteral.getName() + variableLiteral.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse("");
+        } else if (isTypeArg) {
+            tracer = tracer.dive("type arg");
+            String part = variableLiteral.getChild().map(VariableLiteral::getName).orElse("");
+            switch (part) {
+                case "name":
+                    return tracer + "\"" + field.getTypeName() + "\"";
+                case "length":
+                    return tracer + "\"" + ((SimpleTypeReference) field).getSizeInBits() + "\"";
+                case "encoding":
+                    String encoding = ((StringLiteral) field.getEncoding().orElse(new DefaultStringLiteral("UTF-8"))).getValue();
+                    return tracer + "\"" + encoding + "\"";
+                default:
+                    return tracer + "";
+            }
+        } else {
+            return tracer + toVariableExpressionRest(field, resultType, variableLiteral);
+        }
+    }
+
+    private String toGlobalFunctionCallSerializationExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, List<Argument> serialzerArguments, Tracer tracer) {
+        tracer = tracer.dive("GLOBAL_FUNCTION_CALL");
+        StringBuilder sb = new StringBuilder(variableLiteral.getName());
+        if (variableLiteral.getArgs().isPresent()) {
+            sb.append("(");
+            boolean firstArg = true;
+            for (Term arg : variableLiteral.getArgs().get()) {
+                if (!firstArg) {
+                    sb.append(", ");
+                }
+                sb.append(toSerializationExpression(field, ANY_TYPE_REFERENCE, arg, serialzerArguments));
+                firstArg = false;
+                /*if (arg instanceof VariableLiteral) {
+                    VariableLiteral va = (VariableLiteral) arg;
+                    boolean isSerializerArg = "readBuffer".equals(va.getName()) || "writeBuffer".equals(va.getName());
+                    boolean isTypeArg = "_type".equals(va.getName());
+                    if (!isSerializerArg && !isTypeArg && serialzerArguments != null) {
+                        for (Argument serializerArgument : serialzerArguments) {
+                            if (serializerArgument.getName().equals(va.getName())) {
+                                isSerializerArg = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (isSerializerArg) {
+                        sb.append(va.getName()).append(va.getChild().map(child -> "." + toVariableExpressionRest(child)).orElse(""));
+                    } else if (isTypeArg) {
+                        String part = va.getChild().map(VariableLiteral::getName).orElse("");
+                        switch (part) {
+                            case "name":
+                                sb.append("\"").append(field.getTypeName()).append("\"");
+                                break;
+                            case "length":
+                                sb.append("\"").append(((SimpleTypeReference) field).getSizeInBits()).append("\"");
+                                break;
+                            case "encoding":
+                                String encoding = ((StringLiteral) field.getEncoding().orElse(new DefaultStringLiteral("UTF-8"))).getValue();
+                                sb.append("\"").append(encoding).append("\"");
+                                break;
+                        }
+                    } else {
+                        sb.append(toVariableSerializationExpression(field, va, serialzerArguments));
+                    }
+                } else if (arg instanceof StringLiteral) {
+                    sb.append(((StringLiteral) arg).getValue());
+                }*/
+            }
+            sb.append(")");
+        }
+        return tracer + sb.toString();
+    }
+
+    private String toStaticCallSerializationExpression(Field field, TypeReference resultType, VariableLiteral variableLiteral, List<Argument> serialzerArguments, Tracer tracer) {
+        tracer = tracer.dive("STATIC_CALL");
+        StringBuilder sb = new StringBuilder();
+        List<Term> arguments = variableLiteral.getArgs().orElseThrow(() -> new RuntimeException("A STATIC_CALL expression needs arguments"));
+        if (arguments.size() < 1) {
+            throw new RuntimeException("A STATIC_CALL expression expects at least one argument.");
+        }
+        // TODO: make it as static import with a emitImport so if a static call is present a "utils" package must be present in the import
+        sb.append(packageName()).append(".utils.StaticHelper.");
+        // Get the class and method name
+        String methodName = arguments.get(0).asLiteral()
+            .orElseThrow(() -> new RuntimeException("First argument should be a literal"))
+            .asStringLiteral()
+            .orElseThrow(() -> new RuntimeException("Expecting the first argument of a 'STATIC_CALL' to be a StringLiteral")).
+            getValue();
+        //methodName = methodName.substring(1, methodName.length() - 1);
+        sb.append(methodName).append("(");
+        for (int i = 1; i < arguments.size(); i++) {
+            Term arg = arguments.get(i);
+            if (i > 1) {
+                sb.append(", ");
+            }
+            sb.append(toSerializationExpression(field, ANY_TYPE_REFERENCE, arg, serialzerArguments));
+            /*if (arg instanceof VariableLiteral) {
+                VariableLiteral va = (VariableLiteral) arg;
+                // "readBuffer" and "_value" are always available in every parser.
+                boolean isSerializerArg = "readBuffer".equals(va.getName()) || "writeBuffer".equals(va.getName()) || "_value".equals(va.getName()) || "element".equals(va.getName());
+                boolean isTypeArg = "_type".equals(va.getName());
+                if (!isSerializerArg && !isTypeArg && serialzerArguments != null) {
+                    for (Argument serializerArgument : serialzerArguments) {
+                        if (serializerArgument.getName().equals(va.getName())) {
+                            isSerializerArg = true;
+                            break;
+                        }
+                    }
+                }
+                if (isSerializerArg) {
+                    sb.append(va.getName()).append(va.getChild().map(child -> "." + toVariableExpressionRest(child)).orElse(""));
+                } else if (isTypeArg) {
+                    String part = va.getChild().map(VariableLiteral::getName).orElse("");
+                    switch (part) {
+                        case "name":
+                            sb.append("\"").append(field.getTypeName()).append("\"");
+                            break;
+                        case "length":
+                            sb.append("\"").append(((SimpleTypeReference) field).getSizeInBits()).append("\"");
+                            break;
+                        case "encoding":
+                            String encoding = ((StringLiteral) field.getEncoding().orElse(new DefaultStringLiteral("UTF-8"))).getValue();
+                            sb.append("\"").append(encoding).append("\"");
+                            break;
+                    }
+                } else {
+                    sb.append(toVariableSerializationExpression(field, va, serialzerArguments));
+                }
+            } else if (arg instanceof StringLiteral) {
+                sb.append(((StringLiteral) arg).getValue());
+            }*/
+        }
+        sb.append(")");
+        return tracer + sb.toString();
+    }
+
+    private String toVariableExpressionRest(Field field, TypeReference resultType, VariableLiteral variableLiteral) {
+        Tracer tracer = Tracer.start("variable expression rest");
+        // length is kind of a keyword in mspec, so we shouldn't be naming variables length. if we ask for the length of a object we can just return length().
+        // This way we can get the length of a string when serializing
+        String variableLiteralName = variableLiteral.getName();
+        if (variableLiteralName.equals("length")) {
+            tracer = tracer.dive("length");
+            return tracer + variableLiteralName + "()" + ((variableLiteral.getIndex().isPresent() ? ".get(" + variableLiteral.getIndex().orElseThrow() + ")" : "") +
+                variableLiteral.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse(""));
+        }
+        return tracer + "get" + WordUtils.capitalize(variableLiteralName) + "()" + ((variableLiteral.getIndex().isPresent() ? ".get(" + variableLiteral.getIndex().orElseThrow() + ")" : "") +
+            variableLiteral.getChild().map(child -> "." + toVariableExpressionRest(field, resultType, child)).orElse(""));
+    }
+
+    public String getSizeInBits(ComplexTypeDefinition complexTypeDefinition, List<Argument> parserArguments) {
+        int sizeInBits = 0;
+        StringBuilder sb = new StringBuilder();
+        for (Field field : complexTypeDefinition.getFields()) {
+            if (field instanceof ArrayField) {
+                ArrayField arrayField = (ArrayField) field;
+                final SimpleTypeReference type = (SimpleTypeReference) arrayField.getType();
+                switch (arrayField.getLoopType()) {
+                    case COUNT:
+                        sb.append("(").append(toSerializationExpression(null, INT_TYPE_REFERENCE, arrayField.getLoopExpression(), parserArguments)).append(" * ").append(type.getSizeInBits()).append(") + ");
+                        break;
+                    case LENGTH:
+                        sb.append("(").append(toSerializationExpression(null, INT_TYPE_REFERENCE, arrayField.getLoopExpression(), parserArguments)).append(" * 8) + ");
+                        break;
+                    case TERMINATED:
+                        // No terminated.
+                        break;
+                }
+            } else if (field instanceof TypedField) {
+                TypedField typedField = (TypedField) field;
+                final TypeReference type = typedField.getType();
+                if (field instanceof ManualField) {
+                    ManualField manualField = (ManualField) field;
+                    sb.append("(").append(toSerializationExpression(null, INT_TYPE_REFERENCE, manualField.getLengthExpression(), parserArguments)).append(") + ");
+                } else if (type instanceof SimpleTypeReference) {
+                    SimpleTypeReference simpleTypeReference = (SimpleTypeReference) type;
+                    if (simpleTypeReference instanceof VstringTypeReference) {
+                        sb.append(toSerializationExpression(null, INT_TYPE_REFERENCE, ((VstringTypeReference) simpleTypeReference).getLengthExpression(), parserArguments)).append(" + ");
+                    } else {
+                        sizeInBits += simpleTypeReference.getSizeInBits();
+                    }
+                }
+            }
+        }
+        return sb.toString() + sizeInBits;
+    }
+
+    public String escapeValue(TypeReference typeReference, String valueString) {
+        if (valueString == null) {
+            return null;
+        }
+        if (typeReference instanceof SimpleTypeReference) {
+            SimpleTypeReference simpleTypeReference = (SimpleTypeReference) typeReference;
+            switch (simpleTypeReference.getBaseType()) {
+                case UINT:
+                case INT:
+                    // If it's a one character string and is numeric, output it as char.
+                    if (!NumberUtils.isParsable(valueString) && (valueString.length() == 1)) {
+                        return "'" + valueString + "'";
+                    }
+                    break;
+                case STRING:
+                case VSTRING:
+                    return "\"" + valueString + "\"";
+            }
+        }
+        return valueString;
+    }
+
+    public String getFieldOptions(TypedField field, List<Argument> parserArguments) {
+        StringBuilder sb = new StringBuilder();
+        final Optional<Term> encodingOptional = field.getEncoding();
+        if (encodingOptional.isPresent()) {
+            final String encoding = toParseExpression(field, field.getType(), encodingOptional.get(), parserArguments);
+            sb.append(", WithOption.WithEncoding(").append(encoding).append(")");
+        }
+        final Optional<Term> byteOrderOptional = field.getByteOrder();
+        if (byteOrderOptional.isPresent()) {
+            final String byteOrder = toParseExpression(field, field.getType(), byteOrderOptional.get(), parserArguments);
+            sb.append(", WithOption.WithByteOrder(").append(byteOrder).append(")");
+        }
+        return sb.toString();
+    }
+
+    public boolean isBigIntegerSource(Term term) {
+        boolean isBigInteger = term.asLiteral()
+            .flatMap(LiteralConversions::asVariableLiteral)
+            .flatMap(VariableLiteral::getChild)
+            .map(Term.class::cast)
+            .map(this::isBigIntegerSource)
+            .orElse(false);
+        return isBigInteger || term.asLiteral()
+            .flatMap(LiteralConversions::asVariableLiteral)
+            .map(VariableLiteral::getTypeReference)
+            .flatMap(TypeReferenceConversions::asIntegerTypeReference)
+            .map(integerTypeReference -> integerTypeReference.getSizeInBits() >= 64)
+            .orElse(false);
+    }
+
+    public boolean needsLongMarker(Optional<SimpleTypeReference> baseTypeReference) {
+        return baseTypeReference.isPresent() && baseTypeReference.get().isIntegerTypeReference() && baseTypeReference.get().asIntegerTypeReference().orElseThrow().getSizeInBits() >= 32;
+    }
+
+}
diff --git a/code-generation/language-python/src/main/resources/META-INF/services/org.apache.plc4x.plugins.codegenerator.language.LanguageOutput b/code-generation/language-python/src/main/resources/META-INF/services/org.apache.plc4x.plugins.codegenerator.language.LanguageOutput
new file mode 100644
index 0000000000..6af10b1ebd
--- /dev/null
+++ b/code-generation/language-python/src/main/resources/META-INF/services/org.apache.plc4x.plugins.codegenerator.language.LanguageOutput
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+#
+org.apache.plc4x.language.python.PythonLanguageOutput
diff --git a/code-generation/language-python/src/main/resources/templates/python/complex-type-template.python.ftlh b/code-generation/language-python/src/main/resources/templates/python/complex-type-template.python.ftlh
new file mode 100644
index 0000000000..c712b97723
--- /dev/null
+++ b/code-generation/language-python/src/main/resources/templates/python/complex-type-template.python.ftlh
@@ -0,0 +1,977 @@
+<#--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+-->
+<#-- Prevent freemarker from escaping stuff -->
+<#outputformat "undefined">
+<#-- Declare the name and type of variables passed in to the template -->
+<#-- @ftlvariable name="languageName" type="java.lang.String" -->
+<#-- @ftlvariable name="protocolName" type="java.lang.String" -->
+<#-- @ftlvariable name="outputFlavor" type="java.lang.String" -->
+<#-- @ftlvariable name="helper" type="org.apache.plc4x.language.java.JavaLanguageTemplateHelper" -->
+<#-- @ftlvariable name="tracer" type="org.apache.plc4x.plugins.codegenerator.protocol.freemarker.Tracer" -->
+<#-- @ftlvariable name="type" type="org.apache.plc4x.plugins.codegenerator.types.definitions.ComplexTypeDefinition" -->
+<#-- Declare the name and type of variables declared locally inside the template -->
+${helper.packageName(protocolName, languageName, outputFlavor)?replace(".", "/")}/${type.name}.java
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 ${helper.packageName(protocolName, languageName, outputFlavor)};
+
+import static org.apache.plc4x.java.spi.generation.StaticHelper.*;
+import static org.apache.plc4x.java.spi.codegen.io.DataReaderFactory.*;
+import static org.apache.plc4x.java.spi.codegen.io.DataWriterFactory.*;
+import static org.apache.plc4x.java.spi.codegen.fields.FieldReaderFactory.*;
+import static org.apache.plc4x.java.spi.codegen.fields.FieldWriterFactory.*;
+
+import org.apache.plc4x.java.spi.codegen.*;
+import org.apache.plc4x.java.spi.codegen.io.*;
+import org.apache.plc4x.java.spi.codegen.fields.*;
+import org.apache.plc4x.java.api.exceptions.*;
+import org.apache.plc4x.java.spi.generation.*;
+import org.apache.plc4x.java.api.value.*;
+
+import java.time.*;
+import java.util.*;
+import java.math.BigInteger;
+
+// Code generated by code-generation. DO NOT EDIT.
+
+<#-- TODO: the code below implies that parserArguments will be null if not present... not pretty  -->
+<#if type.parserArguments.isPresent()><#assign parserArguments=type.allParserArguments.orElseThrow()></#if>
+public<#if type.isDiscriminatedParentTypeDefinition()> abstract</#if> class ${type.name}<#if type.isDiscriminatedParentTypeDefinition()></#if><#if type.parentType.isPresent()> extends ${type.parentType.orElseThrow().name}</#if> implements Message {
+
+<#--
+    If this is a discriminated child type, we need to generate methods for accessing it's discriminator
+    values, as if they were normal java properties.
+-->
+<#if type.isDiscriminatedChildTypeDefinition()>
+    <#assign discriminatedChildType = type.asDiscriminatedComplexTypeDefinition().orElseThrow()>
+    // Accessors for discriminator values.
+    <#list discriminatedChildType.getDiscriminatorMap() as discriminatorName, discriminatorValue>
+        <#-- If the discriminator name matches that of another field, suppress the methods generation -->
+        <#if !discriminatedChildType.isNonDiscriminatorField(discriminatorName)><#--&& !discriminatedChildType.isParserArgument(discriminatorName)-->
+            <#assign discriminatorType = helper.getDiscriminatorTypes()[discriminatorName]>
+    public ${helper.getLanguageTypeNameForTypeReference(discriminatorType)} get${discriminatorName?cap_first}() {
+            <#if discriminatorValue?? && !helper.isWildcard(discriminatorValue)>
+                <#if discriminatorType.isEnumTypeReference()>
+        return ${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(null, discriminatorType, discriminatorValue, parserArguments)};
+                <#else>
+        return (${helper.getLanguageTypeNameForTypeReference(discriminatorType, true)}) ${helper.toParseExpression(null, discriminatorType, discriminatorValue, parserArguments)};
+                </#if>
+            <#else>
+        return ${helper.getNullValueForTypeReference(discriminatorType)};
+            </#if>
+    }
+        </#if>
+    </#list>
+</#if>
+<#--
+    If this is a discriminated parent type, we need to generate the abstract methods for accessing it's
+    discriminator values instead.
+-->
+<#if type.isDiscriminatedParentTypeDefinition()>
+    <#assign discriminatedParentType = type>
+    <#-- @ftlvariable name="discriminatedParentType" type="org.apache.plc4x.plugins.codegenerator.types.definitions.ComplexTypeDefinition" -->
+    // Abstract accessors for discriminator values.
+    <#list helper.discriminatorTypes as discriminatorName, discriminatorType>
+        <#-- If the discriminator name matches that of another field, suppress the methods generation -->
+        <#if !type.isNonDiscriminatorField(discriminatorName)><#-- && !type.isParserArgument(discriminatorName)-->
+    public abstract ${helper.getLanguageTypeNameForTypeReference(discriminatorType)} get${discriminatorName?cap_first}();
+        </#if>
+    </#list>
+</#if>
+<#-- If the current type contains "const" fields, generate some java constants for holing their values -->
+<#if type.constFields?has_content>
+
+    // Constant values.
+    <#list type.constFields as field>
+    public static final ${helper.getLanguageTypeNameForTypeReference(field.type)} ${field.name?upper_case} = ${helper.toParseExpression(field, field.type, field.referenceValue, parserArguments)};
+    </#list>
+</#if>
+<#-- Property fields are fields that require a property in the pojo -->
+<#if type.propertyFields?has_content>
+
+    // Properties.
+    <#list type.propertyFields as field>
+    protected final ${helper.getLanguageTypeNameForTypeReference(field.type, !field.isOptionalField())} ${field.name};
+    </#list>
+</#if>
+<#if parserArguments?has_content>
+  <#assign filteredParserArguments=parserArguments?filter(arg -> !type.isDiscriminatorField(arg.name) && !type.getPropertyFieldFromThisOrParentByName(arg.name).isPresent())>
+</#if>
+<#if filteredParserArguments?has_content>
+
+    // Arguments.
+    <#list filteredParserArguments as parserArgument>
+    protected final ${helper.getLanguageTypeNameForTypeReference(parserArgument.type)} ${parserArgument.name};
+    </#list>
+</#if>
+<#assign reservedFields=type.getFields()?filter(f->f.isReservedField())>
+<#if reservedFields?has_content>
+    // Reserved Fields
+    <#list reservedFields as reservedField>
+    private ${helper.getLanguageTypeNameForTypeReference(reservedField.asReservedField().orElseThrow().type, false)} reservedField${reservedField?index};
+    </#list>
+</#if>
+
+    <#-- getAllPropertyFields() returns not only the property fields of this type but also of it's parents -->
+    <@compress single_line=true>
+    public ${type.name}(
+        <#list type.getAllPropertyFields() as field>
+            ${helper.getLanguageTypeNameForField(field)} ${field.name}
+            <#sep>, </#sep>
+        </#list>
+        <#if filteredParserArguments?has_content>
+            <#if type.getAllPropertyFields()?has_content>, </#if>
+            <#list filteredParserArguments as parserArgument>
+                ${helper.getLanguageTypeNameForTypeReference(parserArgument.type)} ${parserArgument.name}
+                <#sep>, </#sep>
+            </#list>
+        </#if>
+        ) {
+    </...@compress>
+
+    <@compress single_line=true>
+        super(
+        <#if type.parentPropertyFields?has_content>
+            <#list type.parentPropertyFields as field>
+                ${field.name}
+                <#sep>, </#sep>
+            </#list>
+        </#if>
+        <#if type.parentType.isPresent() && type.parentType.orElseThrow().allParserArguments.isPresent()>
+            <#assign filteredParentParserArguments = type.parentType.orElseThrow().allParserArguments.orElseThrow()?filter(arg -> !type.parentType.orElseThrow().asComplexTypeDefinition().orElseThrow().isDiscriminatorField(arg.name))>
+            <#if filteredParentParserArguments?has_content>
+                <#if type.parentPropertyFields?has_content>, </#if>
+                <#list filteredParentParserArguments as parserArgument>
+                    ${parserArgument.name}
+                    <#sep>, </#sep>
+                </#list>
+            </#if>
+        </#if>
+        );
+    </...@compress>
+
+<#list type.propertyFields as field>
+        this.${field.name} = ${field.name};
+</#list>
+<#if filteredParserArguments?has_content>
+    <#list filteredParserArguments as parserArgument>
+        this.${parserArgument.name} = ${parserArgument.name};
+    </#list>
+</#if>
+    }
+
+<#list type.abstractFields as field>
+    public abstract ${helper.getLanguageTypeNameForField(field)} get${field.asNamedField().orElseThrow().name?cap_first}();
+
+</#list>
+<#list type.propertyFields as field>
+    public ${helper.getLanguageTypeNameForField(field)} get${field.name?cap_first}() {
+        return ${field.name};
+    }
+
+</#list>
+<#list type.virtualFields as field>
+    public ${helper.getLanguageTypeNameForField(field)} get${field.name?cap_first}() {
+        <#if helper.getLanguageTypeNameForField(field) = 'String'>
+        return ${helper.getLanguageTypeNameForField(field)}.valueOf(${helper.toSerializationExpression(field, field.type, field.valueExpression, parserArguments)});
+        <#--elseif helper.getLanguageTypeNameForField(field) = 'BigInteger' && !helper.isBigIntegerSource(field.valueExpression)-->
+        <#elseif helper.getLanguageTypeNameForField(field) = 'BigInteger'>
+        Object o = ${helper.toSerializationExpression(field, field.type, field.valueExpression, parserArguments)};
+        if (o instanceof BigInteger)
+            return (BigInteger) o;
+        return BigInteger.valueOf(((Number)o).longValue());
+        <#else>
+        return (${helper.getLanguageTypeNameForField(field)}) (${helper.toSerializationExpression(field, field.type, field.valueExpression, parserArguments)});
+        </#if>
+    }
+
+</#list>
+<#list type.constFields as field>
+    public ${helper.getLanguageTypeNameForField(field)} get${field.name?cap_first}() {
+        return ${field.name?upper_case};
+    }
+
+</#list>
+
+    <#if outputFlavor != "passive">
+<#if type.isDiscriminatedChildTypeDefinition()>
+    @Override
+    protected void serialize${type.parentType.orElseThrow().name}Child(WriteBuffer writeBuffer) throws SerializationException {
+<#else>
+    <#if type.isDiscriminatedParentTypeDefinition()>
+    abstract protected void serialize${type.name?cap_first}Child(WriteBuffer writeBuffer) throws SerializationException;
+
+    </#if>
+    public void serialize(WriteBuffer writeBuffer) throws SerializationException {
+</#if>
+        PositionAware positionAware = writeBuffer;
+        <#if helper.hasFieldOfType("unknown")>
+            throw new SerializationException("Unknown field not serializable");
+        <#else>
+            int startPos = positionAware.getPos();
+            writeBuffer.pushContext("${type.name}");
+            <#assign reservedFieldIndex=0>
+            <#list type.fields as field>
+                <#switch field.typeName>
+                    <#case "array">
+                        <#assign arrayField = field.asArrayField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Array Field (${arrayField.name})
+                        <#if arrayField.type.elementTypeReference.isByteBased()>
+                        writeByteArrayField("${namedField.name}", ${namedField.name}, writeByteArray(writeBuffer, 8));
+                        <#elseif arrayField.type.elementTypeReference.isSimpleTypeReference()>
+                        writeSimpleTypeArrayField("${namedField.name}", ${namedField.name}, ${helper.getDataWriterCall(arrayField.type.elementTypeReference, namedField.name)});
+                        <#else>
+                        writeComplexTypeArrayField("${namedField.name}", ${namedField.name}, writeBuffer);
+                        </#if>
+                        <#break>
+                    <#case "checksum">
+                        <#assign checksumField = field.asChecksumField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Checksum Field (checksum) (Calculated)
+                        writeChecksumField("${namedField.name}", (${helper.getLanguageTypeNameForField(field)}) (${helper.toParseExpression(checksumField, checksumField.type, checksumField.checksumExpression, parserArguments)}), ${helper.getDataWriterCall(typedField.type, namedField.name)});
+                        <#break>
+                    <#case "const">
+                        <#assign constField = field.asConstField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Const Field (${constField.name})
+                        <#if typedField.type.isEnumTypeReference()>writeConstField("${constField.name}", ${namedField.name?upper_case}.getValue(), ${helper.getDataWriterCall(helper.getEnumBaseTypeReference(typedField.type), namedField.name)});<#else>writeConstField("${constField.name}", ${namedField.name?upper_case}, ${helper.getDataWriterCall(typedField.type, namedField.name)});</#if>
+                        <#break>
+                    <#case "discriminator">
+                        <#assign discriminatorField = field.asDiscriminatorField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Discriminator Field (${discriminatorField.name}) (Used as input to a switch field)
+                        <#if typedField.type.isEnumTypeReference()>writeDiscriminatorEnumField("${namedField.name}", "${helper.getLanguageTypeNameForField(field)}", get${discriminatorField.name?cap_first}(), ${helper.getEnumDataWriterCall(typedField.type, namedField.name, "value")});<#else>writeDiscriminatorField("${namedField.name}", get${discriminatorField.name?cap_first}(), ${helper.getDataWriterCall(typedField.type, namedField.name)});</#if>
+                        <#break>
+                    <#case "enum">
+                        <#assign enumField = field.asEnumField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Enum field (${namedField.name})
+                        writeEnumField("${namedField.name}", "${helper.getLanguageTypeNameForField(field)}", ${namedField.name}, ${helper.getEnumDataWriterCall(typedField.type, namedField.name, enumField.fieldName)});
+                        <#break>
+                    <#case "implicit">
+                        <#assign implicitField = field.asImplicitField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Implicit Field (${implicitField.name}) (Used for parsing, but its value is not stored as it's implicitly given by the objects content)
+                        <#-- Implicit field values might be used in expressions, in order to avoid problems, we generate a temporary variable with the given name. -->
+                        ${helper.getLanguageTypeNameForField(field)} ${implicitField.name} = (${helper.getLanguageTypeNameForField(field)}) (${helper.toSerializationExpression(implicitField, implicitField.type, implicitField.serializeExpression, parserArguments)});
+                        writeImplicitField("${namedField.name}", ${implicitField.name}, ${helper.getDataWriterCall(typedField.type, namedField.name)});
+                        <#break>
+                    <#case "manualArray">
+                        <#assign manualArrayField = field.asManualArrayField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Manual Array Field (${manualArrayField.name})
+                        writeManualArrayField("${namedField.name}", ${namedField.name}, (${helper.getLanguageTypeNameForTypeReference(manualArrayField.type.elementTypeReference)} _value) -> ${helper.toParseExpression(manualArrayField, manualArrayField.type.elementTypeReference, manualArrayField.serializeExpression, parserArguments)}, writeBuffer);
+                        <#break>
+                    <#case "manual">
+                        <#assign manualField = field.asManualField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Manual Field (${manualField.name})
+                        writeManualField("${namedField.name}", () -> ${helper.toParseExpression(manualField, manualField.type, manualField.serializeExpression, parserArguments)}, writeBuffer);
+                        <#break>
+                    <#case "optional">
+                        <#assign optionalField = field.asOptionalField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Optional Field (${optionalField.name}) (Can be skipped, if the value is null)
+                        <#if optionalField.type.isEnumTypeReference()>
+                            writeOptionalEnumField("${optionalField.name}", "${helper.getLanguageTypeNameForField(field)}", ${optionalField.name}, ${helper.getEnumDataWriterCall(optionalField.type, optionalField.name, "value")}<#if optionalField.conditionExpression.present>, ${helper.toSerializationExpression(optionalField, helper.boolTypeReference, optionalField.conditionExpression.get(), parserArguments)}</#if>);
+                        <#elseif optionalField.type.isDataIoTypeReference()>
+                            writeOptionalField("${optionalField.name}", ${optionalField.name}, new DataWriterDataIoDefault(writeBuffer, (wb, val) -> ${optionalField.type.asComplexTypeReference().orElseThrow().name}.staticSerialize(wb, val<#if optionalField.type.asComplexTypeReference().orElseThrow().params?has_content>, <#list optionalField.type.asComplexTypeReference().orElseThrow().params.orElseThrow() as param>${helper.toParseExpression(optionalField, helper.anyTypeReference, param, p [...]
+                        <#else>
+                            writeOptionalField("${optionalField.name}", ${optionalField.name}, ${helper.getDataWriterCall(typedField.type, optionalField.name)}<#if optionalField.conditionExpression.present>, ${helper.toSerializationExpression(optionalField, helper.boolTypeReference, optionalField.conditionExpression.get(), parserArguments)}</#if>);
+                        </#if>
+                        <#break>
+                    <#case "padding">
+                        <#assign paddingField = field.asPaddingField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+
+                        // Padding Field (padding)
+                        writePaddingField("padding", (int) (${helper.toParseExpression(paddingField, helper.intTypeReference, paddingField.paddingCondition, parserArguments)}), (${helper.getLanguageTypeNameForField(field)}) ${helper.toSerializationExpression(paddingField, paddingField.type, paddingField.paddingValue, parserArguments)}, ${helper.getDataWriterCall(typedField.type, "padding")});
+                        <#break>
+                    <#case "reserved">
+                        <#assign reservedField = field.asReservedField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+
+                        // Reserved Field (reserved)
+                        writeReservedField("reserved", reservedField${reservedFieldIndex}!=null?reservedField${reservedFieldIndex}:${helper.getReservedValue(reservedField)}, ${helper.getDataWriterCall(typedField.type, "reserved")});<#assign reservedFieldIndex=reservedFieldIndex+1>
+                        <#break>
+                    <#case "simple">
+                        <#assign simpleField = field.asSimpleField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Simple Field (${namedField.name})
+                        <#if typedField.type.isEnumTypeReference()>
+                            writeSimpleEnumField("${simpleField.name}", "${helper.getLanguageTypeNameForField(field)}", ${simpleField.name}, ${helper.getEnumDataWriterCall(simpleField.type, simpleField.name, "value")});
+                        <#elseif simpleField.type.isDataIoTypeReference()>
+                            writeSimpleField("${simpleField.name}", ${simpleField.name}, new DataWriterDataIoDefault(writeBuffer, (wb, val) -> ${simpleField.type.asComplexTypeReference().orElseThrow().name}.staticSerialize(wb, val<#if simpleField.type.asComplexTypeReference().orElseThrow().params?has_content>, <#list simpleField.type.asComplexTypeReference().orElseThrow().params.orElseThrow() as param>${helper.toParseExpression(simpleField, helper.anyTypeReference, param, parserArguments [...]
+                        <#else>
+                            writeSimpleField("${simpleField.name}", ${simpleField.name}, ${helper.getDataWriterCall(typedField.type, simpleField.name)}${helper.getFieldOptions(typedField, parserArguments)});</#if>
+                        <#break>
+                    <#case "switch">
+                        <#assign switchField = field.asSwitchField().orElseThrow()>
+
+                        // Switch field (Serialize the sub-type)
+                        serialize${type.name?cap_first}Child(writeBuffer);
+                        <#break>
+                    <#case "virtual">
+                        <#assign virtualField = field.asVirtualField().orElseThrow()>
+                        <#assign typedField = field.asTypedField().orElseThrow()>
+                        <#assign namedField = field.asNamedField().orElseThrow()>
+
+                        // Virtual field (doesn't actually serialize anything, just makes the value available)
+                        ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = get${namedField.name?cap_first}();
+                        writeBuffer.writeVirtual("${namedField.name}", ${namedField.name});
+                        <#break>
+                </#switch>
+            </#list>
+
+            writeBuffer.popContext("${type.name}");
+        </#if>
+        }
+    </#if>
+
+    @Override
+    public int getLengthInBytes() {
+        return (int) Math.ceil((float) getLengthInBits() / 8.0);
+    }
+
+    @Override
+    public int getLengthInBits() {
+        int lengthInBits = <#if type.parentType.isPresent()>super.getLengthInBits()<#else>0</#if>;
+        ${type.name} _value  = this;
+<#list type.fields as field>
+<#switch field.typeName>
+    <#case "array">
+        <#assign arrayField = field.asArrayField().orElseThrow()>
+        <#assign arrayElementTypeReference = arrayField.type.asArrayTypeReference().orElseThrow().getElementTypeReference()>
+
+        // Array field
+        if(${arrayField.name} != null) {
+        <#if arrayElementTypeReference.isSimpleTypeReference()>
+            <#assign simpleTypeReference = arrayElementTypeReference.asSimpleTypeReference().orElseThrow()>
+            lengthInBits += ${simpleTypeReference.sizeInBits} * ${arrayField.name}.<#if arrayElementTypeReference.isByteBased()>length<#else>size()</#if>;
+        <#elseif arrayField.isCountArrayField()>
+            int i=0;
+            <#assign nonSimpleTypeReference = arrayElementTypeReference.asNonSimpleTypeReference().orElseThrow()>
+            for(${nonSimpleTypeReference.name} element : ${arrayField.name}) {
+                boolean last = ++i >= ${arrayField.name}.size();
+                lengthInBits += element.getLengthInBits();
+            }
+        <#else>
+            for(Message element : ${arrayField.name}) {
+                lengthInBits += element.getLengthInBits();
+            }
+        </#if>
+        }
+        <#break>
+    <#case "checksum">
+        <#assign checksumField = field.asChecksumField().orElseThrow()>
+        <#assign typedField = field.asTypedField().orElseThrow()>
+        <#assign simpleTypeReference = typedField.type.asSimpleTypeReference().orElseThrow()>
+
+        // Checksum Field (checksum)
+        lengthInBits += ${simpleTypeReference.sizeInBits};
+        <#break>
+    <#case "const">
+        <#assign constField = field.asConstField().orElseThrow()>
+        <#assign typedField = field.asTypedField().orElseThrow()>
+
+        // Const Field (${constField.name})
+        <#if typedField.type.isSimpleTypeReference()>
+        <#assign simpleTypeReference = typedField.type.asSimpleTypeReference().orElseThrow()>
+        lengthInBits += ${simpleTypeReference.sizeInBits};
+        <#else>
+        lengthInBits += ${helper.getEnumBaseTypeReference(typedField.type).sizeInBits};
+        </#if>
+        <#break>
+    <#case "discriminator">
+        <#assign discriminatorField = field.asDiscriminatorField().orElseThrow()>
+
+        // Discriminator Field (${discriminatorField.name})
+        <#if discriminatorField.type.isSimpleTypeReference()>
+            <#assign simpleTypeReference = discriminatorField.type.asSimpleTypeReference().orElseThrow()>
+            <#if simpleTypeReference.isVstringTypeReference()>
+                <#assign vstringTypeReference = simpleTypeReference.asVstringTypeReference().orElseThrow()>
+        lengthInBits += ${helper.toSerializationExpression(discriminatorField, helper.intTypeReference, vstringTypeReference.getLengthExpression(), parserArguments)};
+            <#else>
+        lengthInBits += ${simpleTypeReference.sizeInBits};
+            </#if>
+        <#elseif helper.isEnumField(field)>
+            lengthInBits += ${helper.getEnumBaseTypeReference(discriminatorField.type).sizeInBits};
+        <#else>
+            lengthInBits += ${discriminatorField.name}.getLengthInBits();
+        </#if>
+        <#break>
+    <#case "enum">
+        <#assign enumField = field.asEnumField().orElseThrow()>
+
+        // Enum Field (${enumField.name})
+        lengthInBits += ${helper.getEnumBaseTypeReference(enumField.type).sizeInBits};
+        <#break>
+    <#case "implicit">
+        <#assign implicitField = field.asImplicitField().orElseThrow()>
+        <#assign simpleTypeReference = implicitField.type.asSimpleTypeReference().orElseThrow()>
+
+        // Implicit Field (${implicitField.name})
+        lengthInBits += ${simpleTypeReference.sizeInBits};
+        <#break>
+    <#case "manualArray">
+        <#assign manualArrayField = field.asManualArrayField().orElseThrow()>
+        <#assign arrayElementTypeReference = manualArrayField.type.asArrayTypeReference().orElseThrow().getElementTypeReference()>
+
+        // Manual Array Field (${manualArrayField.name})
+        lengthInBits += ${helper.toParseExpression(manualArrayField, helper.intTypeReference, manualArrayField.lengthExpression, parserArguments)} * 8;
+        <#break>
+    <#case "manual">
+        <#assign manualField = field.asManualField().orElseThrow()>
+
+        // Manual Field (${manualField.name})
+        lengthInBits += ${helper.toParseExpression(manualField, helper.intTypeReference, manualField.lengthExpression, parserArguments)};
+        <#break>
+    <#case "optional">
+        <#assign optionalField = field.asOptionalField().orElseThrow()>
+
+        // Optional Field (${optionalField.name})
+        if(${optionalField.name} != null) {
+        <#if optionalField.type.isSimpleTypeReference()>
+            <#assign simpleTypeReference = optionalField.type.asSimpleTypeReference().orElseThrow()>
+            <#if simpleTypeReference.isVstringTypeReference()>
+                <#assign vstringTypeReference = simpleTypeReference.asVstringTypeReference().orElseThrow()>
+            lengthInBits += ${helper.toSerializationExpression(optionalField, helper.intTypeReference, vstringTypeReference.getLengthExpression(), parserArguments)};
+            <#else>
+            lengthInBits += ${simpleTypeReference.sizeInBits};
+            </#if>
+        <#elseif helper.isEnumField(field)>
+            lengthInBits += ${helper.getEnumBaseTypeReference(optionalField.type).sizeInBits};
+        <#elseif optionalField.type.isDataIoTypeReference()>
+            lengthInBits += ${optionalField.type.asComplexTypeReference().orElseThrow().name}.getLengthInBits(${optionalField.name}<#if optionalField.type.asComplexTypeReference().orElseThrow().params?has_content>, <#list optionalField.type.asComplexTypeReference().orElseThrow().params.orElseThrow() as param>${helper.toParseExpression(optionalField, helper.anyTypeReference, param, parserArguments)}<#sep>, </#sep></#list></#if>);
+        <#else>
+            lengthInBits += ${optionalField.name}.getLengthInBits();
+        </#if>
+        }
+        <#break>
+    <#case "padding">
+        <#assign paddingField = field.asPaddingField().orElseThrow()>
+        <#assign simpleTypeReference = paddingField.type.asSimpleTypeReference().orElseThrow()>
+
+        // Padding Field (padding)
+        <#-- We're replacing the "lastItem" with 'false' here as the item itself can't know if it is the last -->
+        int _timesPadding = (int) (${helper.toParseExpression(paddingField, helper.intTypeReference, paddingField.paddingCondition, parserArguments)});
+        while (_timesPadding-- > 0) {
+            lengthInBits += ${simpleTypeReference.sizeInBits};
+        }
+        <#break>
+    <#case "reserved">
+        <#assign reservedField = field.asReservedField().orElseThrow()>
+        <#assign simpleTypeReference = reservedField.type.asSimpleTypeReference().orElseThrow()>
+
+        // Reserved Field (reserved)
+        lengthInBits += ${simpleTypeReference.sizeInBits};
+        <#break>
+    <#case "simple">
+        <#assign simpleField = field.asSimpleField().orElseThrow()>
+
+        // Simple field (${simpleField.name})
+        <#if simpleField.type.isSimpleTypeReference()>
+            <#assign simpleTypeReference = simpleField.type.asSimpleTypeReference().orElseThrow()>
+            <#if simpleTypeReference.isVstringTypeReference()>
+                <#assign vstringTypeReference = simpleTypeReference.asVstringTypeReference().orElseThrow()>
+        lengthInBits += ${helper.toSerializationExpression(simpleField, helper.intTypeReference, vstringTypeReference.getLengthExpression(), parserArguments)};
+            <#else>
+        lengthInBits += ${simpleTypeReference.sizeInBits};
+            </#if>
+        <#elseif helper.isEnumField(field)>
+        lengthInBits += ${helper.getEnumBaseTypeReference(simpleField.type).sizeInBits};
+        <#elseif simpleField.type.isDataIoTypeReference()>
+        lengthInBits += ${simpleField.type.asComplexTypeReference().orElseThrow().name}.getLengthInBits(${simpleField.name}<#if simpleField.type.asComplexTypeReference().orElseThrow().params?has_content>, <#list simpleField.type.asComplexTypeReference().orElseThrow().params.orElseThrow() as param>${helper.toParseExpression(simpleField, helper.anyTypeReference, param, parserArguments)}<#sep>, </#sep></#list></#if>);
+        <#else>
+        lengthInBits += ${simpleField.name}.getLengthInBits();
+        </#if>
+        <#break>
+    <#case "switch">
+        <#assign switchField = field.asSwitchField().orElseThrow()>
+
+        // Length of sub-type elements will be added by sub-type...
+        <#break>
+    <#case "unknown">
+        <#assign unknownField = field.asUnknownField().orElseThrow()>
+        <#assign simpleTypeReference = unknownField.type.asSimpleTypeReference().orElseThrow()>
+
+        // Unknown field
+        lengthInBits += ${simpleTypeReference.sizeInBits};
+        <#break>
+    <#case "virtual">
+        <#assign virtualField = field.asVirtualField().orElseThrow()>
+
+        // A virtual field doesn't have any in- or output.
+        <#break>
+</#switch>
+</#list>
+
+        return lengthInBits;
+    }
+
+<#-- The parse and serialize methods here are just proxies for forwardning the requests to static counterparts -->
+    <#if !type.isDiscriminatedChildTypeDefinition()>
+    public static ${type.name} staticParse(ReadBuffer readBuffer, Object... args) throws ParseException {
+        PositionAware positionAware = readBuffer;
+        <#if parserArguments?has_content>
+        if((args == null) || (args.length != ${parserArguments?size})) {
+            throw new PlcRuntimeException("Wrong number of arguments, expected ${parserArguments?size}, but got " + args.length);
+        }
+            <#list parserArguments as parserArgument>
+                <#assign languageName=helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)>
+        ${languageName} ${parserArgument.name};
+        if(args[${parserArgument?index}] instanceof ${languageName}) {
+            ${parserArgument.name} = (${languageName}) args[${parserArgument?index}];
+                <#if parserArgument.type.isSimpleTypeReference() || parserArgument.type.isEnumTypeReference()>
+        } else if (args[${parserArgument?index}] instanceof String) {
+            ${parserArgument.name} = ${languageName}.valueOf((String) args[${parserArgument?index}]);
+                </#if>
+        } else {
+            throw new PlcRuntimeException("Argument ${parserArgument?index} expected to be of type ${languageName} or a string which is parseable but was " + args[${parserArgument?index}].getClass().getName());
+        }
+            </#list>
+        </#if>
+        return staticParse(readBuffer<#if parserArguments?has_content>, <#list parserArguments as parserArgument>${parserArgument.name}<#sep>, </#sep></#list></#if>);
+    }
+
+    </#if>
+<#-- Here come the actual parse and serialize methods that actually do the parsing and serlaizing -->
+    <#assign hasParserArguments=parserArguments?has_content/>
+    <#assign parserArgumentList><#if hasParserArguments><#list parserArguments as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if></#assign>
+    public static ${type.name}<#if type.isDiscriminatedChildTypeDefinition()>Builder staticParseBuilder<#else> staticParse</#if>(ReadBuffer readBuffer<#if hasParserArguments>, ${parserArgumentList}</#if>) throws ParseException {
+        readBuffer.pullContext("${type.name}");
+        PositionAware positionAware = readBuffer;
+        int startPos = positionAware.getPos();
+        int curPos;
+    <#assign reservedFieldIndex=0>
+    <#list type.fields as field>
+        <#switch field.typeName>
+            <#case "array">
+                <#assign arrayField = field.asArrayField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+                <#assign arrayElementTypeReference = arrayField.type.asArrayTypeReference().orElseThrow().getElementTypeReference()>
+
+                <#if arrayElementTypeReference.isByteBased()>
+                    <#if !field.isCountArrayField() && !field.isLengthArrayField()>
+                        throw new ParseException("array fields of type byte only support 'count' and 'length' loop-types.");
+                    </#if>
+                    byte[] ${namedField.name} = readBuffer.readByteArray("${namedField.name}", Math.toIntExact(${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
+                <#else>
+                <#-- If this is a count array, we can directly initialize an array with the given size -->
+                    <#if field.isCountArrayField()>
+                        ${helper.getNonPrimitiveLanguageTypeNameForField(arrayField)} ${arrayField.name} = readCountArrayField("${arrayField.name}", ${helper.getDataReaderCall(arrayField.type)}, ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression, parserArguments)}${helper.getFieldOptions(typedField, parserArguments)});
+                    <#-- In all other cases do we have to work with a list, that is later converted to an array -->
+                    <#else>
+                    <#-- For a length array, we read data till the read position of the buffer reaches a given position -->
+                        <#if field.isLengthArrayField()>
+                            ${helper.getNonPrimitiveLanguageTypeNameForField(arrayField)} ${arrayField.name} = readLengthArrayField("${arrayField.name}", ${helper.getDataReaderCall(arrayField.type)}, ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression, parserArguments)}${helper.getFieldOptions(typedField, parserArguments)});
+                        <#-- A terminated array keeps on reading data as long as the termination expression evaluates to false -->
+                        <#elseif field.isTerminatedArrayField()>
+                            ${helper.getNonPrimitiveLanguageTypeNameForField(arrayField)} ${arrayField.name} = readTerminatedArrayField("${arrayField.name}", ${helper.getDataReaderCall(arrayField.type)}, () -> ((boolean) (${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression, parserArguments)}))${helper.getFieldOptions(typedField, parserArguments)});
+                        </#if>
+                    </#if>
+                </#if>
+                <#break>
+            <#case "assert">
+                <#assign assertField = field.asAssertField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}, (${helper.getLanguageTypeNameForField(field)}) (${helper.toParseExpression(assertField, assertField.type, assertField.conditionExpression, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "checksum">
+                <#assign checksumField = field.asChecksumField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}, (${helper.getLanguageTypeNameForField(field)}) (${helper.toParseExpression(checksumField, checksumField.type, checksumField.checksumExpression, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "const">
+                <#assign constField = field.asConstField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}, ${type.name}.${namedField.name?upper_case}${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "discriminator">
+                <#assign discriminatorField = field.asDiscriminatorField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "enum">
+                <#assign enumField = field.asEnumField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", "${enumField.type.asNonSimpleTypeReference().orElseThrow().typeDefinition.name}", readEnum(${enumField.type.asNonSimpleTypeReference().orElseThrow().typeDefinition.name}::firstEnumForField${enumField.fieldName?cap_first}, ${helper.getDataReaderCall(helper.getEnumFieldTypeReference(enumField.type, enumField.fieldName))})${helper.getFieldOptions(typed [...]
+                <#break>
+            <#case "implicit">
+                <#assign implicitField = field.asImplicitField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "manualArray">
+                <#assign manualArrayField = field.asManualArrayField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+                <#assign arrayElementTypeReference = manualArrayField.type.asArrayTypeReference().orElseThrow().getElementTypeReference()>
+
+                <#if arrayElementTypeReference.isByteBased()>
+                    byte[] ${namedField.name} = readManualByteArrayField("${namedField.name}", readBuffer, (byte[] _values) -> (boolean) (${helper.toParseExpression(manualArrayField, helper.boolTypeReference, manualArrayField.loopExpression, parserArguments)}), () -> (byte) (${helper.toParseExpression(manualArrayField, manualArrayField.type, manualArrayField.parseExpression, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
+                <#else>
+                    ${helper.getNonPrimitiveLanguageTypeNameForField(manualArrayField)} ${namedField.name} = readManualArrayField("${namedField.name}", readBuffer, (${helper.getNonPrimitiveLanguageTypeNameForField(manualArrayField)} _values) -> (boolean) (${helper.toParseExpression(manualArrayField, helper.boolTypeReference, manualArrayField.loopExpression, parserArguments)}), () -> (${helper.getLanguageTypeNameForTypeReference(manualArrayField.type.elementTypeReference)}) (${helper.toPa [...]
+                </#if>
+                <#break>
+            <#case "manual">
+                <#assign manualField = field.asManualField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${manualField.name} = readManualField("${namedField.name}", readBuffer, () -> (${helper.getLanguageTypeNameForField(manualField)}) (${helper.toParseExpression(manualField, manualField.type, manualField.parseExpression, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "optional">
+                <#assign optionalField = field.asOptionalField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}<#if optionalField.conditionExpression.present>, ${helper.toParseExpression(optionalField, helper.boolTypeReference, optionalField.conditionExpression.get(), parserArguments)}</#if>${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "padding">
+                <#assign paddingField = field.asPaddingField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign simpleTypeReference = paddingField.type.asSimpleTypeReference().orElseThrow()>
+
+                read${field.typeName?cap_first}Field(${helper.getDataReaderCall(typedField.type)}, (int) (${helper.toParseExpression(paddingField, paddingField.type, paddingField.paddingCondition, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "reserved">
+                <#assign reservedField = field.asReservedField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForTypeReference(reservedField.type, false)} reservedField${reservedFieldIndex}<#assign reservedFieldIndex=reservedFieldIndex+1> = read${field.typeName?cap_first}Field("reserved", ${helper.getDataReaderCall(typedField.type)}, ${helper.getReservedValue(reservedField)}${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "simple">
+                <#assign simpleField = field.asSimpleField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = <#if typedField.type.isEnumTypeReference()>readEnumField("${namedField.name}", "${helper.getLanguageTypeNameForField(field)}", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});<#else>read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});</#if>
+                <#break>
+            <#case "switch">
+                <#assign switchField = field.asSwitchField().orElseThrow()>
+
+                // Switch Field (Depending on the discriminator values, passes the instantiation to a sub-type)
+                ${type.name}Builder builder = null;
+                <#list switchField.cases as case>
+                    <@compress single_line=true>
+                        <#if case.discriminatorValueTerms?has_content>
+                            if(
+                            <#list case.discriminatorValueTerms as discriminatorValueTerm>
+                                <#if helper.isWildcard(discriminatorValueTerm)>
+                                    true
+                                <#else>
+                                    <#assign discriminatorExpression=switchField.discriminatorExpressions[discriminatorValueTerm?index].asLiteral().orElseThrow().asVariableLiteral().orElseThrow()>
+                                    <#assign discriminatorType=helper.getDiscriminatorTypes()[discriminatorExpression.discriminatorName]>
+                                    EvaluationHelper.equals(
+                                    ${helper.toParseExpression(switchField, discriminatorType, discriminatorExpression, parserArguments)},
+                                    <#if discriminatorType.isEnumTypeReference()>
+                                        ${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(switchField, discriminatorType, discriminatorValueTerm, parserArguments)}
+                                    <#else>
+                                        (${helper.getLanguageTypeNameForTypeReference(discriminatorType, true)}) ${helper.toParseExpression(switchField, discriminatorType, discriminatorValueTerm, parserArguments)}
+                                    </#if>
+                                    )
+                                </#if>
+                                <#sep> && </#sep>
+                            </#list>
+                            )
+                        </#if>{
+                    </...@compress>
+                    <@compress single_line=true>
+                        <#assign hasCaseParseArguments=case.allParserArguments.isPresent() && case.allParserArguments.orElseThrow()?has_content>
+                        <#assign caseParseArguments><#if hasCaseParseArguments><#list case.allParserArguments.orElseThrow() as parserArgument>${parserArgument.name}<#sep>, </#sep></#list></#if></#assign>
+                        builder = ${case.name}.staticParseBuilder(readBuffer<#if hasCaseParseArguments>, ${tracer.dive("case parse arguments")} ${caseParseArguments}</#if>);
+                    </...@compress>
+                    }<#sep> else </#sep>
+                </#list>
+                if (builder == null) {
+                    throw new ParseException("Unsupported case for discriminated type"<#if switchField.getDiscriminatorExpressions()?has_content>+" parameters ["<#list switchField.getDiscriminatorExpressions() as discriminatorExpression>+"${discriminatorExpression.stringRepresentation()}="+${helper.toParseExpression(null, null, discriminatorExpression, parserArguments)}<#sep>+" "</#sep></#list>+"]"</#if>);
+                }
+                <#break>
+            <#case "unknown">
+                <#assign unknownField = field.asUnknownField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+
+                read${field.typeName?cap_first}Field("unknown", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "virtual">
+                <#assign virtualField = field.asVirtualField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getLanguageTypeNameForField(field)}.class, ${helper.toParseExpression(virtualField, virtualField.type, virtualField.valueExpression, parserArguments)}${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+            <#case "validation">
+                <#assign validationField = field.asValidationField().orElseThrow()>
+                // Validation
+                if (!(${helper.toParseExpression(validationField, helper.boolTypeReference, validationField.getValidationExpression(), null)})) {
+                    <#assign errorType="ParseValidationException">
+                    <#if !validationField.shouldFail()><#assign errorType="ParseAssertException"></#if>
+                    throw new ${errorType}(${validationField.getDescription().orElse("\"Validation failed\"")});
+                }
+                <#break>
+            <#case "peek">
+                <#assign peekField = field.asPeekField().orElseThrow()>
+                <#assign typedField = field.asTypedField().orElseThrow()>
+                <#assign namedField = field.asNamedField().orElseThrow()>
+
+                ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}<#if peekField.offsetExpression.present>, ${helper.toParseExpression(peekField, helper.boolTypeReference, peekField.offsetExpression.get(), parserArguments)}</#if>${helper.getFieldOptions(typedField, parserArguments)});
+                <#break>
+        </#switch>
+    </#list>
+
+    readBuffer.closeContext("${type.name}");
+    // Create the instance
+    <#if type.isDiscriminatedChildTypeDefinition()>
+        return new ${type.name}Builder(
+        <#list type.propertyFields as field>
+            ${field.name}<#sep>, </#sep>
+        </#list>
+        <#if filteredParserArguments?has_content>
+            <#if type.propertyFields?has_content>, </#if>
+            <#list filteredParserArguments as arg>${arg.name}<#sep>, </#sep>
+            </#list>
+        </#if>
+        <#if (type.propertyFields?has_content || filteredParentParserArguments?has_content) && reservedFields?has_content>,</#if>
+        <#list reservedFields as reservedField>
+            reservedField${reservedField?index}<#sep>, </#sep>
+        </#list>
+        );
+    <#elseif type.isDiscriminatedParentTypeDefinition()>
+        ${type.name} _${type.name?uncap_first} = builder.build(
+        <#list type.propertyFields as field>
+            ${field.name}<#sep>, </#sep>
+        </#list>
+        <#if filteredParserArguments?has_content>
+            <#if type.propertyFields?has_content>, </#if>
+            <#list filteredParserArguments as arg>
+                ${arg.name}<#sep>, </#sep>
+            </#list>
+        </#if>
+        );
+        <#list reservedFields as reservedField>
+        _${type.name?uncap_first}.reservedField${reservedField?index} = reservedField${reservedField?index};
+        </#list>
+        return _${type.name?uncap_first};
+    <#else>
+        ${type.name} _${type.name?uncap_first};
+        _${type.name?uncap_first} = new ${type.name}(
+        <#list type.propertyFields as field>
+            ${field.name}<#sep>, </#sep>
+        </#list>
+        <#if filteredParserArguments?has_content>
+            <#if type.propertyFields?has_content>, </#if>
+            <#list filteredParserArguments as arg>
+                ${arg.name}<#sep>, </#sep>
+            </#list>
+        </#if>
+        );
+        <#list reservedFields as reservedField>
+            _${type.name?uncap_first}.reservedField${reservedField?index} = reservedField${reservedField?index};
+        </#list>
+        return _${type.name?uncap_first};
+    </#if>
+    }
+
+    <#if type.isDiscriminatedParentTypeDefinition()>
+        public static interface ${type.name}Builder {
+            ${type.name} build(
+        <#list type.propertyFields as field>
+            ${helper.getLanguageTypeNameForField(field)} ${field.name}<#sep>, </#sep>
+            </#list>
+        <#if filteredParserArguments?has_content>
+            <#if type.propertyFields?has_content>, </#if>
+            <#list filteredParserArguments as arg>
+                ${helper.getLanguageTypeNameForTypeReference(arg.type)} ${arg.name}<#sep>, </#sep>
+            </#list>
+        </#if>
+        );
+        }
+
+    </#if>
+    <#if type.isDiscriminatedChildTypeDefinition()>
+        public static class ${type.name}Builder implements ${type.parentType.orElseThrow().name}.${type.parentType.orElseThrow().name}Builder {
+        <#if type.propertyFields?has_content>
+            <#list type.propertyFields as field>
+        private final ${helper.getLanguageTypeNameForField(field)} ${field.name};
+            </#list>
+        </#if>
+        <#if filteredParserArguments?has_content>
+            <#list filteredParserArguments as arg>
+        private final ${helper.getLanguageTypeNameForTypeReference(arg.type)} ${arg.name};
+            </#list>
+        </#if>
+        <#list reservedFields as reservedField>
+        private final ${helper.getLanguageTypeNameForTypeReference(reservedField.type, false)} reservedField${reservedField?index};
+        </#list>
+
+        public ${type.name}Builder(
+        <#list type.propertyFields as field>
+            ${helper.getLanguageTypeNameForField(field)} ${field.name}<#sep>, </#sep>
+        </#list>
+        <#if filteredParserArguments?has_content>
+            <#if type.propertyFields?has_content>, </#if>
+            <#list filteredParserArguments as arg>
+                ${helper.getLanguageTypeNameForTypeReference(arg.type)} ${arg.name}<#sep>, </#sep>
+            </#list>
+        </#if>
+        <#if (type.propertyFields?has_content || filteredParentParserArguments?has_content) && reservedFields?has_content>,</#if>
+        <#list reservedFields as reservedField>
+            ${helper.getLanguageTypeNameForTypeReference(reservedField.type, false)} reservedField${reservedField?index}<#sep>, </#sep>
+        </#list>
+        ) {
+        <#list type.propertyFields as field>
+            this.${field.name} = ${field.name};
+        </#list>
+        <#if filteredParserArguments?has_content>
+            <#list filteredParserArguments as arg>
+            this.${arg.name} = ${arg.name};
+            </#list>
+        </#if>
+        <#list reservedFields as reservedField>
+            this.reservedField${reservedField?index} = reservedField${reservedField?index};
+        </#list>
+        }
+
+        public ${type.name} build(
+        <#list type.parentType.orElseThrow().asComplexTypeDefinition().orElseThrow().propertyFields as field>
+            ${helper.getLanguageTypeNameForField(field)} ${field.name}<#sep>, </#sep>
+        </#list>
+        <#if filteredParentParserArguments?has_content>
+            <#if type.parentType.orElseThrow().asComplexTypeDefinition().orElseThrow().propertyFields?has_content>, </#if>
+                <#list filteredParentParserArguments as arg>
+                ${helper.getLanguageTypeNameForTypeReference(arg.type)} ${arg.name}<#sep>, </#sep>
+                </#list>
+            </#if>
+        ) {
+            ${type.name} ${type.name?uncap_first} = new ${type.name}(
+            <#list type.allPropertyFields as field>
+                ${field.name}<#sep>, </#sep>
+            </#list>
+        <#if filteredParserArguments?has_content>
+            <#if type.allPropertyFields?has_content>, </#if>
+            <#list filteredParserArguments as arg>
+                ${arg.name}<#sep>, </#sep>
+            </#list>
+        </#if>);
+        <#list reservedFields as reservedField>
+            ${type.name?uncap_first}.reservedField${reservedField?index} = reservedField${reservedField?index};
+        </#list>
+            return ${type.name?uncap_first};
+        }
+    }
+
+    </#if>
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof ${type.name})) {
+            return false;
+        }
+        ${type.name} that = (${type.name}) o;
+        return
+            <#if type.propertyFields?has_content>
+            <#list type.propertyFields as field>
+            (get${field.name?cap_first}() == that.get${field.name?cap_first}()) &&
+            </#list>
+            </#if>
+            <#if type.parentType.isPresent()>
+            super.equals(that) &&
+            </#if>
+            true;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+            <#if type.parentType.isPresent()>
+            super.hashCode()<#if type.propertyFields?has_content>,</#if>
+            </#if>
+            <#if type.propertyFields?has_content>
+            <#list type.propertyFields as field>
+            get${field.name?cap_first}()<#sep>,</#sep>
+            </#list>
+            </#if>
+        );
+    }
+
+    @Override
+    public String toString() {
+        WriteBufferBoxBased writeBufferBoxBased = new WriteBufferBoxBased(true, true);
+        try {
+            writeBufferBoxBased.writeSerializable(this);
+        } catch (SerializationException e) {
+            throw new RuntimeException(e);
+        }
+        return "\n" + writeBufferBoxBased.getBox().toString()+ "\n";
+    }
+}
+</#outputformat>
\ No newline at end of file
diff --git a/code-generation/language-python/src/main/resources/templates/python/data-io-template.python.ftlh b/code-generation/language-python/src/main/resources/templates/python/data-io-template.python.ftlh
new file mode 100644
index 0000000000..7779e6e266
--- /dev/null
+++ b/code-generation/language-python/src/main/resources/templates/python/data-io-template.python.ftlh
@@ -0,0 +1,522 @@
+<#--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+-->
+<#-- Prevent freemarker from escaping stuff -->
+<#outputformat "undefined">
+<#-- Declare the name and type of variables passed in to the template -->
+<#-- @ftlvariable name="languageName" type="java.lang.String" -->
+<#-- @ftlvariable name="protocolName" type="java.lang.String" -->
+<#-- @ftlvariable name="outputFlavor" type="java.lang.String" -->
+<#-- @ftlvariable name="helper" type="org.apache.plc4x.language.java.JavaLanguageTemplateHelper" -->
+<#-- @ftlvariable name="tracer" type="org.apache.plc4x.plugins.codegenerator.protocol.freemarker.Tracer" -->
+<#-- @ftlvariable name="type" type="org.apache.plc4x.plugins.codegenerator.types.definitions.ComplexTypeDefinition" -->
+${helper.packageName(protocolName, languageName, outputFlavor)?replace(".", "/")}/${type.name}.java
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 ${helper.packageName(protocolName, languageName, outputFlavor)};
+
+import static org.apache.plc4x.java.spi.generation.StaticHelper.*;
+
+import org.apache.plc4x.java.api.model.PlcTag;
+import org.apache.plc4x.java.api.value.*;
+import org.apache.plc4x.java.spi.generation.EvaluationHelper;
+import org.apache.plc4x.java.spi.generation.ParseException;
+import org.apache.plc4x.java.spi.generation.SerializationException;
+import org.apache.plc4x.java.spi.generation.ReadBuffer;
+import org.apache.plc4x.java.spi.generation.WriteBuffer;
+import org.apache.plc4x.java.spi.generation.ByteOrder;
+import ${helper.packageName(protocolName, languageName, outputFlavor)}.*;
+import org.apache.plc4x.java.spi.values.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.math.BigInteger;
+import java.time.*;
+import java.util.*;
+import java.util.function.Supplier;
+
+// Code generated by code-generation. DO NOT EDIT.
+
+<#-- TODO: the code below implies that parserArguments will be null if not present... not pretty  -->
+<#if type.parserArguments.isPresent()><#assign parserArguments=type.parserArguments.orElseThrow()></#if>
+public class ${type.name} {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(${type.name}.class);
+    public static PlcValue staticParse(ReadBuffer readBuffer<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>) throws ParseException {
+        <#assign defaultCaseOutput=false>
+        <#assign dataIoTypeDefinition=type.asDataIoTypeDefinition().orElseThrow()>
+        <#list dataIoTypeDefinition.switchField.orElseThrow().cases as case>
+            <@compress single_line=true>
+                <#if case.discriminatorValueTerms?has_content>
+                    if(
+                    <#list case.discriminatorValueTerms as discriminatorValueTerm>
+                        <#assign discriminatorExpression=dataIoTypeDefinition.switchField.orElseThrow().discriminatorExpressions[discriminatorValueTerm?index].asLiteral().orElseThrow().asVariableLiteral().orElseThrow()>
+                        <#assign discriminatorType=helper.getDiscriminatorTypes()[discriminatorExpression.name]>
+                        EvaluationHelper.equals(
+                        ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorExpression, parserArguments)},
+                        <#if discriminatorType.isEnumTypeReference()>
+                        ${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
+                        <#else>
+                        ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
+                        </#if>
+                    )
+                        <#sep> && </#sep>
+                    </#list>
+                    )
+                <#else>
+                    <#assign defaultCaseOutput=true>
+                </#if>
+            </...@compress> { // ${case.name}
+            <#assign valueDefined=false>
+            <#list case.fields as field>
+                <#switch field.typeName>
+                    <#case "array">
+                        <#assign arrayField = field.asArrayField().orElseThrow()>
+                        <#assign elementTypeReference=arrayField.type.elementTypeReference>
+            // Array field (${arrayField.name})
+            <#-- Only update curPos if the length expression uses it -->
+                        <#if arrayField.loopExpression.contains("curPos")>
+            curPos = readBuffer.getPos() - startPos;
+                        </#if>
+            <#-- If this is a count array, we can directly initialize an array with the given size -->
+                        <#if field.isCountArrayField()>
+            // Count array
+            if(${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression,parserArguments)} > Integer.MAX_VALUE) {
+                throw new ParseException("Array count of " + (${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression,parserArguments)}) + " exceeds the maximum allowed count of " + Integer.MAX_VALUE);
+            }
+            List<PlcValue> ${arrayField.name};
+            {
+                int itemCount = (int) ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression,parserArguments)};
+                ${arrayField.name} = new LinkedList<>();
+                for(int curItem = 0; curItem < itemCount; curItem++) {
+                    ${arrayField.name}.add(new ${helper.getPlcValueTypeForTypeReference(elementTypeReference)}((${helper.getLanguageTypeNameForTypeReference(elementTypeReference, false)}) <#if elementTypeReference.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)})<#else>${elementTypeReference.asComplexTypeReference().orElseThrow().name}IO.staticParse(readBuffer<#if elementTypeReference.params.isPresen [...]
+                }
+            }
+            <#-- In all other cases do we have to work with a list, that is later converted to an array -->
+                        <#else>
+            <#-- For a length array, we read data till the read position of the buffer reaches a given position -->
+                            <#if arrayField.isLengthArrayField()>
+            // Length array
+            long _${arrayField.name}Length = ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression,parserArguments)};
+            long ${arrayField.name}EndPos = readBuffer.getPos() + _${arrayField.name}Length;
+            List<PlcValue> value = new LinkedList<>();
+            while(readBuffer.getPos() < ${arrayField.name}EndPos) {
+                value.add(
+                <#if elementTypeReference.isSimpleTypeReference()>
+                    new ${helper.getPlcValueTypeForTypeReference(elementTypeReference)}(${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)})
+                <#else>${elementTypeReference.asNonSimpleTypeReference().orElseThrow().name}IO.staticParse(readBuffer
+                    <#if elementTypeReference.params.isPresent()>,
+                        <#list elementTypeReference.params.orElseThrow() as parserArgument>
+                            (${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(elementTypeReference, parserArgument?index), true)}) (${helper.toParseExpression(arrayField, elementTypeReference, parserArgument,parserArguments)})
+                            <#sep>, </#sep>
+                        </#list>
+                    </#if>
+                    )
+                </#if>
+                );
+            }
+            <#-- A terminated array keeps on reading data as long as the termination expression evaluates to false -->
+                            <#elseif arrayField.isTerminatedArrayField()>
+            // Terminated array
+            ${helper.getNonPrimitiveLanguageTypeNameForField(arrayField)} ${arrayField.name} = new LinkedList<>();
+            while(!((boolean) (${helper.toParseExpression(arrayField, helper.boolTypeReference, arrayField.loopExpression,parserArguments)}))) {
+                ${arrayField.name}.add(<#if elementTypeReference.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)}<#else>${elementTypeReference.asComplexTypeReference().orElseThrow().name}IO.staticParse(readBuffer<#if arrayField.params.isPresent()>, <#list arrayField.params.orElseThrow() as parserArgument>(${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(elementTypeReference, parser [...]
+
+                <#-- After parsing, update the current position, but only if it's needed -->
+                                <#if arrayField.loopExpression.contains("curPos")>
+                curPos = readBuffer.getPos() - startPos;
+                                </#if>
+            }
+                            </#if>
+                        </#if>
+                        <#if arrayField.name == "value">
+                            <#assign valueDefined=true>
+                        </#if>
+                    <#break>
+                    <#case "const">
+                        <#assign constField=field.asConstField().orElseThrow()>
+
+            // Const Field (${constField.name})
+            ${helper.getNonPrimitiveLanguageTypeNameForField(constField)} ${constField.name} = ${helper.getReadBufferReadMethodCall(constField.type.asSimpleTypeReference().orElseThrow(), "", constField)};
+            if(${constField.name} != ${dataIoTypeDefinition.name}.${constField.name?upper_case}) {
+                throw new ParseException("Expected constant value " + ${dataIoTypeDefinition.name}.${constField.name?upper_case} + " but got " + ${constField.name});
+            }
+                        <#if constField.name == "value">
+                            <#assign valueDefined=true>
+                        </#if>
+                    <#break>
+                    <#case "enum">
+                        <#assign enumField=field.asEnumField().orElseThrow()>
+
+            // Enum field (${enumField.name})
+            ${helper.getNonPrimitiveLanguageTypeNameForField(enumField)} ${enumField.name} = ${helper.getNonPrimitiveLanguageTypeNameForField(enumField)}.enumForValue(${helper.getReadBufferReadMethodCall(helper.getEnumBaseTypeReference(enumField.type.asSimpleTypeReference().orElseThrow()), "", enumField)});
+                        <#if enumField.name == "value">
+                            <#assign valueDefined=true>
+                        </#if>
+                    <#break>
+                    <#case "manual">
+                        <#assign manualField=field.asManualField().orElseThrow()>
+
+            // Manual Field (${manualField.name})
+            ${helper.getLanguageTypeNameForField(field)} ${manualField.name} = (${helper.getLanguageTypeNameForField(manualField)}) (${helper.toParseExpression(manualField, manualField.type, manualField.parseExpression,parserArguments)});
+                        <#if manualField.name == "value">
+                            <#assign valueDefined=true>
+                        </#if>
+                    <#break>
+                    <#case "reserved">
+                        <#assign reservedField=field.asReservedField().orElseThrow()>
+
+            // Reserved Field (Compartmentalized so the "reserved" variable can't leak)
+            {
+                ${helper.getLanguageTypeNameForField(field)} reserved = ${helper.getReadBufferReadMethodCall(reservedField.type.asSimpleTypeReference().orElseThrow(), "", reservedField)};
+                if(reserved != ${helper.getReservedValue(reservedField)}) {
+                    LOGGER.info("Expected constant value " + ${reservedField.referenceValue} + " but got " + reserved + " for reserved field.");
+                }
+            }
+                    <#break>
+                    <#case "simple">
+                        <#assign simpleField=field.asSimpleField().orElseThrow()>
+
+                        <#if helper.isEnumField(simpleField)>
+            // Enum field (${simpleField.name})
+            ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)} ${simpleField.name} = ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)}.enumForValue(${helper.getReadBufferReadMethodCall(helper.getEnumBaseTypeReference(simpleField.type), "", simpleField)});
+                        <#else>
+            // Simple Field (${simpleField.name})
+            ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)} ${simpleField.name} = <#if simpleField.type.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(simpleField.type.asSimpleTypeReference().orElseThrow(), "", simpleField)}<#else>${simpleField.type.asComplexTypeReference().orElseThrow().name}IO.staticParse(readBuffer<#if simpleField.params.isPresent()>, <#list field.params.orElseThrow() as parserArgument>(${helper.getLanguageTypeNameForTypeReference(helper.g [...]
+                        </#if>
+                        <#if case.name == "Struct" ||
+                            ((case.name == "DATE_AND_TIME") && ((simpleField.name == "year") || (simpleField.name == "month") || (simpleField.name == "day") || (simpleField.name == "hour") || (simpleField.name == "minutes") || (simpleField.name == "seconds"))) ||
+                            ((case.name == "DATE_AND_TIME") && (simpleField.name == "secondsSinceEpoch")) ||
+                            ((case.name == "DATE") && ((simpleField.name == "year") || (simpleField.name == "month") || (simpleField.name == "day"))) ||
+                            ((case.name == "TIME_OF_DAY") && ((simpleField.name == "hour") || (simpleField.name == "minutes") || (simpleField.name == "seconds"))) ||
+                        simpleField.name == "value">
+                            <#assign valueDefined=true>
+                        </#if>
+                    <#break>
+                </#switch>
+            </#list>
+            <#if case.name == "Struct">
+
+                <#-- In this case we need to wrap each field in a PlcValue that matches it's natural type -->
+            Map<String, PlcValue> _map = new HashMap<>();
+                <#list case.fields as field>
+                    <#if field.isArrayField()>
+                        <#assign field=field.asArrayField().orElseThrow()>
+            _map.put("${field.name}", new PlcList(${field.name}));
+                    <#elseif field.isPropertyField()>
+                        <#assign field=field.asPropertyField().orElseThrow()>
+                        <#switch helper.getLanguageTypeNameForTypeReference(field.type)>
+                            <#case "Boolean">
+            _map.put("${field.name}", new PlcBOOL(${field.name}));
+                                <#break>
+                            <#case "Byte">
+            _map.put("${field.name}", new PlcSINT(${field.name}));
+                                <#break>
+                            <#case "Short">
+            _map.put("${field.name}", new PlcINT(${field.name}));
+                                <#break>
+                            <#case "Integer">
+            _map.put("${field.name}", new PlcDINT(${field.name}));
+                                <#break>
+                            <#case "Long">
+            _map.put("${field.name}", new PlcLINT(${field.name}));
+                                <#break>
+                            <#case "BigInteger">
+            _map.put("${field.name}", new PlcBigInteger(${field.name}));
+                                <#break>
+                            <#case "Float">
+            _map.put("${field.name}", new PlcREAL(${field.name}));
+                                <#break>
+                            <#case "Double">
+            _map.put("${field.name}", new PlcLREAL(${field.name}));
+                                <#break>
+                            <#case "BigDecimal">
+            _map.put("${field.name}", new PlcBigDecimal(${field.name}));
+                                <#break>
+                            <#case "String">
+            _map.put("${field.name}", new PlcSTRING(${field.name}));
+                                <#break>
+                            <#case "LocalTime">
+            _map.put("${field.name}", new PlcTIME_OF_DAY(${field.name}));
+                                <#break>
+                            <#case "LocalDate">
+            _map.put("${field.name}", new PlcDATE(${field.name}));
+                                <#break>
+                            <#case "LocalDateTime">
+            _map.put("${field.name}", new PlcDATE_AND_TIME(${field.name}));
+                                <#break>
+                        </#switch>
+                    </#if>
+                </#list>
+                <#assign valueDefined=true>
+            </#if>
+
+            <#if valueDefined>
+                <#switch case.name>
+                    <#case "TIME">
+            return new PlcTIME(value);
+                    <#break>
+                    <#case "DATE">
+                    <#if helper.hasFieldsWithNames(case.fields, "year", "month", "day")>
+            LocalDate value = LocalDate.of(year.intValue(), (month == 0) ? 1 : month.intValue(), (day == 0) ? 1 : day.intValue());
+                    </#if>
+            return new PlcDATE(value);
+                    <#break>
+                    <#case "TIME_OF_DAY">
+                    <#if helper.hasFieldsWithNames(case.fields, "hour", "minutes", "seconds", "nanos")>
+            LocalTime value = LocalTime.of(hour.intValue(), minutes.intValue(), seconds.intValue(), nanos.intValue());
+                    <#elseif helper.hasFieldsWithNames(case.fields, "hour", "minutes", "seconds")>
+            LocalTime value = LocalTime.of(hour.intValue(), minutes.intValue(), seconds.intValue());
+                    </#if>
+            return new PlcTIME_OF_DAY(value);
+                    <#break>
+                    <#case "DATE_AND_TIME">
+                    <#if helper.hasFieldsWithNames(case.fields, "year", "month", "day", "hour", "minutes", "seconds", "nanos")>
+            LocalDateTime value = LocalDateTime.of(year.intValue(), (month == 0) ? 1 : month.intValue(), (day == 0) ? 1 : day.intValue(), hour.intValue(), minutes.intValue(), seconds.intValue(), nanos.intValue());
+                    <#elseif helper.hasFieldsWithNames(case.fields, "year", "month", "day", "hour", "minutes", "seconds")>
+            LocalDateTime value = LocalDateTime.of(year.intValue(), (month == 0) ? 1 : month.intValue(), (day == 0) ? 1 : day.intValue(), hour.intValue(), minutes.intValue(), seconds.intValue());
+                    <#elseif helper.hasFieldsWithNames(case.fields, "secondsSinceEpoch")>
+            LocalDateTime value = LocalDateTime.ofEpochSecond(secondsSinceEpoch, 0, ZoneOffset.UTC);
+                    </#if>
+            return new PlcDATE_AND_TIME(value);
+                    <#break>
+                    <#case "Struct">
+            return new PlcStruct(_map);
+                    <#break>
+                    <#case "List">
+            return new PlcList(value);
+                    <#break>
+                    <#default>
+            return new Plc${case.name}(value);
+                </#switch>
+            </#if>
+        }<#sep> else </#sep></#list>
+        <#if !defaultCaseOutput>
+        return null;
+        </#if>
+    }
+
+<#if outputFlavor != "passive">
+    public static void staticSerialize(WriteBuffer writeBuffer, PlcValue _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>) throws SerializationException {
+        staticSerialize(writeBuffer, _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${parserArgument.name}<#sep>, </#sep></#list></#if>, ByteOrder.BIG_ENDIAN);
+    }
+
+    public static void staticSerialize(WriteBuffer writeBuffer, PlcValue _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>, ByteOrder byteOrder) throws SerializationException {
+        <#assign defaultCaseOutput=false>
+        <#assign dataIoTypeDefinition=type.asDataIoTypeDefinition().orElseThrow()>
+        <#list dataIoTypeDefinition.switchField.orElseThrow().cases as case>
+        <@compress single_line=true>
+            <#if case.discriminatorValueTerms?has_content>
+                if(
+                <#list case.discriminatorValueTerms as discriminatorValueTerm>
+                    <#assign discriminatorExpression=dataIoTypeDefinition.switchField.orElseThrow().discriminatorExpressions[discriminatorValueTerm?index].asLiteral().orElseThrow().asVariableLiteral().orElseThrow()>
+                    <#assign discriminatorType=helper.getDiscriminatorTypes()[discriminatorExpression.name]>
+                    EvaluationHelper.equals(
+                    ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorExpression, parserArguments)},
+                    <#if discriminatorType.isEnumTypeReference()>
+                    ${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
+                    <#else>
+                    ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
+                    </#if>
+                    )
+                    <#sep> && </#sep>
+                </#list>
+                )
+            <#else>
+                <#assign defaultCaseOutput=true>
+            </#if>
+        </...@compress> { // ${case.name}
+            <#list case.fields as field>
+                <#switch field.typeName>
+                    <#case "array">
+                        <#assign arrayField=field.asArrayField().orElseThrow()>
+                        <#assign elementTypeReference=arrayField.type.elementTypeReference>
+            PlcList values = (PlcList) _value;
+
+                        <#if case.name == "Struct">
+            for (PlcValue val : ((List<PlcValue>) values.getStruct().get("${arrayField.name}").getList())) {
+                <#if elementTypeReference.isByteBased()>
+                ${helper.getLanguageTypeNameForField(arrayField)} value = (${helper.getLanguageTypeNameForField(arrayField)}) val.getRaw();
+                writeBuffer.writeByteArray("", value);
+                <#else>
+                ${helper.getLanguageTypeNameForField(arrayField)} value = (${helper.getLanguageTypeNameForField(arrayField)}) val.get${helper.getLanguageTypeNameForField(arrayField)?cap_first}();
+                ${helper.getWriteBufferWriteMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "value", arrayField)};
+                </#if>
+            }
+                        <#else>
+            for (PlcValue val : ((List<PlcValue>) values.getList())) {
+                <#if elementTypeReference.isByteBased()>
+                byte[] value = (byte[]) val.getRaw();
+                writeBuffer.writeByteArray("", value);
+                <#else>
+                ${helper.getLanguageTypeNameForTypeReference(elementTypeReference)} value = (${helper.getLanguageTypeNameForTypeReference(elementTypeReference)}) val.get${helper.getLanguageTypeNameForTypeReference(elementTypeReference)?cap_first}();
+                ${helper.getWriteBufferWriteMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "(" + arrayField.name + ")", arrayField)};
+                </#if>
+            }
+                        </#if>
+
+                    <#if case.name == "BOOL">
+            while (writeBuffer.getPos() < writeBuffer.getData().length) {
+                writeBuffer.writeBit(false);
+            }
+                    </#if>
+                    <#break>
+                    <#case "const">
+                        <#assign constField=field.asConstField().orElseThrow()>
+            // Const Field (${constField.name})
+            ${helper.getWriteBufferWriteMethodCall(constField.type.asSimpleTypeReference().orElseThrow(), constField.referenceValue, constField)};
+                    <#break>
+                    <#case "enum">
+                        <#assign enumField=field.asEnumField().orElseThrow()>
+            // Enum field (${enumField.name})
+            ${helper.getLanguageTypeNameForField(field)} ${enumField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.get${enumField.name?cap_first}();
+            ${helper.getWriteBufferWriteMethodCall(helper.getEnumBaseTypeReference(field.asTypedField().orElseThrow().type), "(" + enumField.name + ".getValue())", enumField)};
+                    <#break>
+                    <#case "manual">
+                        <#assign manualField=field.asManualField().orElseThrow()>
+            // Manual Field (${manualField.name})
+            ${helper.toSerializationExpression(manualField, manualField.type, manualField.serializeExpression, type.parserArguments.orElse(null))};
+                    <#break>
+                    <#case "reserved">
+                        <#assign reservedField=field.asReservedField().orElseThrow()>
+            // Reserved Field
+            ${helper.getWriteBufferWriteMethodCall(reservedField.type.asSimpleTypeReference().orElseThrow(), helper.getReservedValue(reservedField), reservedField)};
+                    <#break>
+                    <#case "simple">
+                        <#assign simpleField=field.asSimpleField().orElseThrow()>
+            // Simple Field (${simpleField.name})
+                        <#if case.name == "Struct">
+            ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.getStruct().get("${simpleField.name}").get${helper.getLanguageTypeNameForField(simpleField)?cap_first}();
+                        <#else>
+                            <#if simpleField.name == "value">
+            ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.get${helper.getLanguageTypeNameForField(simpleField)?cap_first}();
+                            <#elseif simpleField.name == "secondsSinceEpoch">
+            ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.get${helper.getLanguageTypeNameForField(simpleField)?cap_first}();
+                            <#else>
+                                <#-- Just for now -->
+            ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = ${helper.getNullValueForTypeReference(simpleField.type)};
+                            </#if>
+                        </#if>
+                        <#if simpleField.type.isSimpleTypeReference()>
+            ${helper.getWriteBufferWriteMethodCall(simpleField.type.asSimpleTypeReference().orElseThrow(), "(" + simpleField.name + ")", simpleField)};
+                        <#else>
+            ${simpleField.type.asComplexTypeReference().orElseThrow().name}IO.staticSerialize(writeBuffer, ${simpleField.name});
+                        </#if>
+                    <#break>
+                </#switch>
+            </#list>
+        }<#sep> else </#sep></#list>
+    }
+</#if>
+
+    public static int getLengthInBytes(PlcValue _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>) {
+        return (int) Math.ceil((float) getLengthInBits(_value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${parserArgument.name}<#sep>, </#sep></#list></#if>) / 8.0);
+    }
+
+    public static int getLengthInBits(PlcValue _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>) {
+        int sizeInBits = 0;
+        <#assign defaultCaseOutput=false>
+        <#assign dataIoTypeDefinition=type.asDataIoTypeDefinition().orElseThrow()>
+        <#list dataIoTypeDefinition.switchField.orElseThrow().cases as case>
+        <@compress single_line=true>
+        <#if case.discriminatorValueTerms?has_content>
+        if(
+            <#list case.discriminatorValueTerms as discriminatorValueTerm>
+            <#assign discriminatorExpression=dataIoTypeDefinition.switchField.orElseThrow().discriminatorExpressions[discriminatorValueTerm?index].asLiteral().orElseThrow().asVariableLiteral().orElseThrow()>
+            <#assign discriminatorType=helper.getDiscriminatorTypes()[discriminatorExpression.name]>
+                EvaluationHelper.equals(
+                    ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorExpression, parserArguments)},
+                    <#if discriminatorType.isEnumTypeReference()>
+                    ${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
+                    <#else>
+                    ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
+                    </#if>
+                )
+            <#sep> && </#sep>
+            </#list>
+        )
+        <#else>
+            <#assign defaultCaseOutput=true>
+        </#if>
+        </...@compress> { // ${case.name}
+            <#list case.fields as field>
+            <#switch field.typeName>
+            <#case "array">
+                <#assign arrayField=field.asArrayField().orElseThrow()>
+                <#assign elementTypeReference=arrayField.type.elementTypeReference>
+                PlcList values = (PlcList) _value;
+                <#if case.name == "Struct">
+                // TODO: Finish this!
+                <#elseif elementTypeReference.isComplexTypeReference()>
+                // TODO: Finish this!
+                <#else>
+                sizeInBits += values.getList().size() * ${elementTypeReference.asSimpleTypeReference().orElseThrow().sizeInBits};
+                </#if>
+                <#break>
+            <#case "const">
+                <#assign constField=field.asConstField().orElseThrow()>
+                // Const Field (${constField.name})
+                <#-- const fields are only simple type -->
+                sizeInBits += ${constField.type.asSimpleTypeReference().orElseThrow().sizeInBits};
+                <#break>
+            <#case "enum">
+                <#assign enumField=field.asEnumField().orElseThrow()>
+                // Enum field (${enumField.name})
+                sizeInBits += ${helper.getEnumFieldSimpleTypeReference(enumField.type, enumField.fieldName).sizeInBits};
+                <#break>
+            <#case "manual">
+                <#assign manualField=field.asManualField().orElseThrow()>
+                // Manual Field (${manualField.name})
+                sizeInBits += ${helper.toSerializationExpression(manualField, helper.intTypeReference, manualField.lengthExpression, type.parserArguments.orElse(null))};
+                <#break>
+            <#case "reserved">
+                <#assign reservedField=field.asReservedField().orElseThrow()>
+                // Reserved Field
+                sizeInBits += ${reservedField.type.asSimpleTypeReference().orElseThrow().sizeInBits};
+                <#break>
+            <#case "simple">
+                <#assign simpleField=field.asSimpleField().orElseThrow()>
+                // Simple Field (${simpleField.name})
+                sizeInBits += ${simpleField.type.asSimpleTypeReference().orElseThrow().sizeInBits};
+                <#break>
+            </#switch>
+            </#list>
+        }<#sep> else </#sep></#list>
+        return sizeInBits;
+    }
+
+}
+</#outputformat>
diff --git a/code-generation/language-python/src/main/resources/templates/python/enum-template.python.ftlh b/code-generation/language-python/src/main/resources/templates/python/enum-template.python.ftlh
new file mode 100644
index 0000000000..24da7f185b
--- /dev/null
+++ b/code-generation/language-python/src/main/resources/templates/python/enum-template.python.ftlh
@@ -0,0 +1,164 @@
+<#--
+<#--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+-->
+<#-- Prevent freemarker from escaping stuff -->
+<#outputformat "undefined">
+<#-- Declare the name and type of variables passed in to the template -->
+<#-- @ftlvariable name="languageName" type="java.lang.String" -->
+<#-- @ftlvariable name="protocolName" type="java.lang.String" -->
+<#-- @ftlvariable name="outputFlavor" type="java.lang.String" -->
+<#-- @ftlvariable name="helper" type="org.apache.plc4x.language.java.JavaLanguageTemplateHelper" -->
+<#-- @ftlvariable name="tracer" type="org.apache.plc4x.plugins.codegenerator.protocol.freemarker.Tracer" -->
+<#-- @ftlvariable name="type" type="org.apache.plc4x.plugins.codegenerator.types.definitions.EnumTypeDefinition" -->
+${helper.packageName(protocolName, languageName, outputFlavor)?replace(".", "/")}/${type.name}.java
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 ${helper.packageName(protocolName, languageName, outputFlavor)};
+
+import org.apache.plc4x.java.spi.generation.Message;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+// Code generated by code-generation. DO NOT EDIT.
+
+public enum ${type.name} {
+
+<@compress single_line=true>
+    <#list type.enumValues as enumValue>
+        ${enumValue.name}(
+            <#if type.type.isPresent()>
+                (${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)})
+                <#if type.type.orElseThrow().isNonSimpleTypeReference()>
+                    <#if type.type.orElseThrow().isEnumTypeReference()>
+                        ${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)}.${enumValue.value}
+                    <#else>
+                        ${enumValue.value}
+                    </#if>
+                <#else>
+                    ${enumValue.value}<#if helper.needsLongMarker(type.type)>L</#if>
+                </#if>
+            </#if>
+            <#if type.constantNames?has_content>
+                <#if type.type?has_content>, </#if>
+                <#list type.constantNames as constantName>
+                    <#if type.getConstantType(constantName).isNonSimpleTypeReference()>
+                        <#if helper.escapeValue(type.getConstantType(constantName), enumValue.getConstant(constantName).orElse(null)) == 'null'>
+                            null
+                        <#elseif type.getConstantType(constantName).isEnumTypeReference()>
+                            ${helper.getLanguageTypeNameForTypeReference(type.getConstantType(constantName), true)}.${helper.escapeValue(type.getConstantType(constantName), enumValue.getConstant(constantName).orElseThrow())}<#else>(${helper.getLanguageTypeNameForTypeReference(type.getConstantType(constantName), true)}) ${helper.escapeValue(type.getConstantType(constantName), enumValue.getConstant(constantName).orElseThrow())}</#if><#else>(${helper.getLanguageTypeNameForTypeReference(type [...]
+                        </#if>
+                    <#sep>, </#sep>
+                </#list>
+            </#if>)
+        <#sep>, </#sep>
+    </#list>;
+</...@compress>
+
+<#if type.type.isPresent()>
+    private static final Map<${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), false)}, ${type.name}> map;
+    static {
+        map = new HashMap<>();
+        for (${type.name} value : ${type.name}.values()) {
+            map.put((${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)}) value.getValue(), value);
+        }
+    }
+
+    private ${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)} value;
+</#if>
+<#if type.constantNames?has_content>
+    <#list type.constantNames as constantName>
+        private ${helper.getLanguageTypeNameForTypeReference(type.getConstantType(constantName), true)} ${constantName};
+    </#list>
+</#if>
+
+    ${type.name}(<#if type.type.isPresent()>${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)} value</#if><#if type.constantNames?has_content><#if type.type?has_content>, </#if><#list type.constantNames as constantName>${helper.getLanguageTypeNameForTypeReference(type.getConstantType(constantName), true)} ${constantName}<#sep>, </#sep></#list></#if>) {
+<#if type.type.isPresent()>        this.value = value;</#if>
+<#if type.constantNames?has_content>
+    <#list type.constantNames as constantName>
+        this.${constantName} = ${constantName};
+    </#list>
+</#if>
+    }
+
+<#if type.type.isPresent()>
+    public ${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)} getValue() {
+        return value;
+    }
+</#if>
+
+<#if type.constantNames?has_content>
+    <#list type.constantNames as constantName>
+    public ${helper.getLanguageTypeNameForTypeReference(type.getConstantType(constantName), true)} get${constantName?cap_first}() {
+        return ${constantName};
+    }
+
+    public static ${type.name} firstEnumForField${constantName?cap_first}(${helper.getLanguageTypeNameForTypeReference(type.getConstantType(constantName), true)} fieldValue) {
+        for (${type.name} _val : ${type.name}.values()) {
+            if(_val.get${constantName?cap_first}() == fieldValue) {
+                return _val;
+            }
+        }
+        return null;
+    }
+
+    public static List<${type.name}> enumsForField${constantName?cap_first}(${helper.getLanguageTypeNameForTypeReference(type.getConstantType(constantName), true)} fieldValue) {
+        List<${type.name}> _values = new ArrayList();
+        for (${type.name} _val : ${type.name}.values()) {
+            if(_val.get${constantName?cap_first}() == fieldValue) {
+                _values.add(_val);
+            }
+        }
+        return _values;
+    }
+
+    </#list>
+</#if>
+<#if type.type.isPresent()>
+    public static ${type.name} enumForValue(${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)} value) {
+        return map.get(value);
+    }
+</#if>
+
+<#if type.type.isPresent()>
+    public static Boolean isDefined(${helper.getLanguageTypeNameForTypeReference(type.type.orElseThrow(), true)} value) {
+        return map.containsKey(value);
+    }
+</#if>
+
+}
+</#outputformat>
diff --git a/code-generation/language-python/src/test/resources/integration-test/pom.xml b/code-generation/language-python/src/test/resources/integration-test/pom.xml
new file mode 100644
index 0000000000..4757bda6a1
--- /dev/null
+++ b/code-generation/language-python/src/test/resources/integration-test/pom.xml
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.plc4x</groupId>
+    <artifactId>plc4x-code-generation</artifactId>
+    <version>@project.version@</version>
+    <relativePath>../../../..</relativePath>
+  </parent>
+
+  <artifactId>plc4j-java-mspec-test</artifactId>
+
+  <name>PLC4J: Driver: Java Mspec Test</name>
+  <description></description>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.plc4x.plugins</groupId>
+        <artifactId>plc4x-maven-plugin</artifactId>
+        <version>${plc4x-code-generation.version}</version>
+        <executions>
+          <execution>
+            <id>generate-driver</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>generate-driver</goal>
+            </goals>
+            <configuration>
+              <protocolName>test</protocolName>
+              <languageName>java</languageName>
+              <outputFlavor>read-write</outputFlavor>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.karaf.tooling</groupId>
+        <artifactId>karaf-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>generate-feature-xml</id>
+            <phase>compile</phase>
+            <goals>
+              <!-- Generate the feature.xml -->
+              <goal>features-generate-descriptor</goal>
+              <!-- Check the feature.xml -->
+              <goal>verify</goal>
+            </goals>
+            <configuration>
+              <enableGeneration>true</enableGeneration>
+              <aggregateFeatures>true</aggregateFeatures>
+            </configuration>
+          </execution>
+          <execution>
+            <id>build-kar</id>
+            <phase>package</phase>
+            <goals>
+              <!--
+                Build a kar archive (Jar containing the feature.xml
+                as well as the module content and it's dependencies.
+              -->
+              <goal>kar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
+            <Bundle-Activator>org.apache.plc4x.java.osgi.DriverActivator</Bundle-Activator>
+            <Export-Service>org.apache.plc4x.java.api.PlcDriver,org.apache.plc4x.protocol.test
+            </Export-Service>
+            <Import-Package>
+              com.fasterxml.jackson.annotation;resolution:=optional,
+              *
+            </Import-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <configuration>
+          <usedDependencies combine.children="append">
+            <usedDependency>org.apache.plc4x:plc4x-code-generation-language-java</usedDependency>
+            <usedDependency>org.apache.plc4x:plc4x-code-generation-protocol-test</usedDependency>
+          </usedDependencies>
+        </configuration>
+      </plugin>
+
+      <!--
+          Make the failsafe execute all integration-tests
+        -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-failsafe-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>integration-test</goal>
+              <goal>verify</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <!--
+                Notice the @ instead of the $ as prefix? That's late evaluation.
+          -->
+          <!--argLine>@{failsafeArgLine}</argLine-->
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+<dependencies>
+  <dependency>
+    <groupId>org.apache.plc4x</groupId>
+    <artifactId>plc4j-api</artifactId>
+    <version>@project.version@</version>
+  </dependency>
+
+  <dependency>
+    <groupId>org.apache.plc4x</groupId>
+    <artifactId>plc4j-spi</artifactId>
+    <version>@project.version@</version>
+  </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-transport-tcp</artifactId>
+      <version>@project.version@</version>
+    </dependency>
+
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-buffer</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-annotations</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-utils-test-utils</artifactId>
+      <version>@project.version@</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-language-java</artifactId>
+      <version>@project.version@</version>
+      <!-- Scope is 'provided' as this way it's not shipped with the driver -->
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-protocol-test</artifactId>
+      <version>@project.version@</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-protocol-test</artifactId>
+      <version>@project.version@</version>
+      <classifier>tests</classifier>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+
+  </dependencies>
+
+</project>
+
diff --git a/code-generation/language-python/src/test/resources/integration-test/src/main/java/org/apache/plc4x/java/test/readwrite/utils/StaticHelper.java b/code-generation/language-python/src/test/resources/integration-test/src/main/java/org/apache/plc4x/java/test/readwrite/utils/StaticHelper.java
new file mode 100644
index 0000000000..cc1f621980
--- /dev/null
+++ b/code-generation/language-python/src/test/resources/integration-test/src/main/java/org/apache/plc4x/java/test/readwrite/utils/StaticHelper.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.plc4x.java.test.readwrite.utils;
+
+import org.apache.plc4x.java.spi.generation.*;
+
+public class StaticHelper {
+
+    public static boolean parseBit(ReadBuffer io) {
+        throw new IllegalArgumentException("Hurz!");
+    }
+
+    public static void serializeBit(WriteBuffer io, boolean data) {
+    }
+
+    public static byte parseByte(ReadBuffer io) {
+        throw new IllegalArgumentException("Hurz!");
+    }
+
+    public static void serializeByte(WriteBuffer io, byte data) {
+    }
+
+    public static byte parseInt8(ReadBuffer io) {
+        throw new IllegalArgumentException("Hurz!");
+    }
+
+    public static void serializeInt8(WriteBuffer io, byte data) {
+    }
+
+    public static short parseUint8(ReadBuffer io) {
+        throw new IllegalArgumentException("Hurz!");
+    }
+
+    public static void serializeUint8(WriteBuffer io, short data) {
+    }
+
+    public static float parseFloat(ReadBuffer io) {
+        throw new IllegalArgumentException("Hurz!");
+    }
+
+    public static void serializeFloat(WriteBuffer io, float data) {
+    }
+
+    public static double parseDouble(ReadBuffer io) {
+        throw new IllegalArgumentException("Hurz!");
+    }
+
+    public static void serializeDouble(WriteBuffer io, double data) {
+    }
+
+    public static String parseString(ReadBuffer io) {
+        throw new IllegalArgumentException("Hurz!");
+    }
+
+    public static void serializeString(WriteBuffer io, String data) {
+    }
+
+    public static short readManualField(ReadBuffer io, short simpleField) {
+        return 0;
+    }
+
+    public static void writeManualField(WriteBuffer io, short simpleFlied) {
+
+    }
+
+    public static short crcInt8(int num) {
+        return (byte) num;
+    }
+
+    public static short crcUint8(int num) {
+        return (short) num;
+    }
+
+}
diff --git a/code-generation/language-python/src/test/resources/settings.xml b/code-generation/language-python/src/test/resources/settings.xml
new file mode 100644
index 0000000000..7e9bc3609a
--- /dev/null
+++ b/code-generation/language-python/src/test/resources/settings.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+  -->
+<settings>
+  <profiles>
+    <profile>
+      <id>it-repo</id>
+      <activation>
+        <activeByDefault>true</activeByDefault>
+      </activation>
+      <repositories>
+        <repository>
+          <id>local.central</id>
+          <url>@localRepositoryUrl@</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+      </repositories>
+      <pluginRepositories>
+        <pluginRepository>
+          <id>local.central</id>
+          <url>@localRepositoryUrl@</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </pluginRepository>
+      </pluginRepositories>
+    </profile>
+  </profiles>
+</settings>
\ No newline at end of file
diff --git a/code-generation/pom.xml b/code-generation/pom.xml
index 2f15b8283f..2eee34a58f 100644
--- a/code-generation/pom.xml
+++ b/code-generation/pom.xml
@@ -37,7 +37,6 @@
     <module>language-base-freemarker</module>
     <module>protocol-base-mspec</module>
     <module>protocol-test</module>
-
     <module>language-java</module>
   </modules>
 
@@ -62,6 +61,13 @@
         <module>language-go</module>
       </modules>
     </profile>
+
+    <profile>
+      <id>with-python</id>
+      <modules>
+        <module>language-python</module>
+      </modules>
+    </profile>
   </profiles>
 
   <dependencies>
diff --git a/sandbox/plc4py/pom.xml b/sandbox/plc4py/pom.xml
index a88d5e46f4..db0587c3e8 100644
--- a/sandbox/plc4py/pom.xml
+++ b/sandbox/plc4py/pom.xml
@@ -65,6 +65,25 @@
         </executions>
       </plugin>
 
+      <plugin>
+        <groupId>org.apache.plc4x.plugins</groupId>
+        <artifactId>plc4x-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>generate-modbus-driver</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>generate-driver</goal>
+            </goals>
+            <configuration>
+              <protocolName>modbus</protocolName>
+              <languageName>python</languageName>
+              <outputFlavor>read-write</outputFlavor>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-remote-resources-plugin</artifactId>
@@ -192,6 +211,15 @@
   </build>
 
   <dependencies>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-language-python</artifactId>
+      <version>0.11.0-SNAPSHOT</version>
+      <!-- Scope is 'provided' as this way it's not shipped with the driver -->
+      <scope>provided</scope>
+    </dependency>
+
     <dependency>
       <groupId>org.apache.plc4x</groupId>
       <artifactId>plc4x-protocols-modbus</artifactId>