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>