<#--
  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, parserArguments)}<#sep>, </#sep></#list></#if>))<#if optionalField.conditionExpression.present>, ${helper.toSerializationExpression(optionalField, helper.boolTypeReference, optionalField.conditionExpression.get(), parserArguments)}</#if>);
                        <#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)}<#sep>, </#sep></#list></#if>)));
                        <#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(typedField, parserArguments)});
                <#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.toParseExpression(manualArrayField, manualArrayField.type, manualArrayField.parseExpression, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
                </#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>