You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2026/09/07 13:12:48 UTC

(plc4x) 02/02: fix(plc4j): Retain a discriminator value that no sub type pins

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

sruehl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit 8ed193b9438b419593111b4d59fccd0a70183806
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Mon Sep 7 15:11:45 2026 +0200

    fix(plc4j): Retain a discriminator value that no sub type pins
    
    The same defect as the preceding commit, in the Java generator: a sub type
    whose typeSwitch case leaves a discriminator open had no constant to return,
    so the generator emitted the type's null value and the parsed value was lost.
    For Modbus that meant an exception response reported function code 0 rather
    than the function that failed; for ADS an error response reported a null
    commandId.
    
    The parent now declares a field for each discriminator that some case leaves
    open, staticParse remembers the value while dispatching, and the sub type
    reads it back. The value is held in a boxed local so that only the cases that
    need it assign anything, matching the Go generator, which keeps a sub type
    that pins its own discriminator untouched.
    
    Regenerating every driver that checks in generated code changes exactly the
    same seventeen types the Go generator changed, across modbus, umas, bacnet,
    ads and knxnetip, which is a useful cross-check that the two generators agree
    on which discriminators need retaining.
    
    The Modbus exception response case added in the preceding commit now runs and
    passes here as well. The BACnet cases only get their truncation half in Java,
    because BacnetIpParserTruncationTest deliberately runs no more than that for
    this protocol; their round trip is covered in Go.
---
 .../java/generators/ComplexTypeGenerator.java      | 89 +++++++++++++++++++++-
 .../plc4x/java/ads/readwrite/AdsErrorResponse.java |  4 +-
 .../apache/plc4x/java/ads/readwrite/AmsPacket.java | 23 +++++-
 .../apache/plc4x/java/bacnetip/readwrite/APDU.java | 13 +++-
 .../plc4x/java/bacnetip/readwrite/APDUUnknown.java |  2 +-
 .../readwrite/BACnetConfirmedServiceRequest.java   | 13 +++-
 .../BACnetConfirmedServiceRequestUnknown.java      |  2 +-
 .../readwrite/BACnetUnconfirmedServiceRequest.java | 13 +++-
 .../BACnetUnconfirmedServiceRequestUnknown.java    |  2 +-
 .../apache/plc4x/java/bacnetip/readwrite/NLM.java  | 14 +++-
 .../plc4x/java/bacnetip/readwrite/NLMReserved.java |  2 +-
 .../readwrite/NLMVendorProprietaryMessage.java     |  2 +-
 .../plc4x/java/knxnetip/readwrite/LDataFrame.java  | 13 +++-
 .../java/knxnetip/readwrite/LDataFrameACK.java     |  2 +-
 .../plc4x/java/modbus/readwrite/ModbusPDU.java     | 13 +++-
 .../java/modbus/readwrite/ModbusPDUError.java      |  2 +-
 .../plc4x/java/umas/readwrite/ModbusPDU.java       | 13 +++-
 .../plc4x/java/umas/readwrite/ModbusPDUError.java  |  2 +-
 18 files changed, 205 insertions(+), 19 deletions(-)

diff --git a/code-generation/language/java/src/main/java/org/apache/plc4x/codegeneration/language/java/generators/ComplexTypeGenerator.java b/code-generation/language/java/src/main/java/org/apache/plc4x/codegeneration/language/java/generators/ComplexTypeGenerator.java
index 805eb74b43..d85b42999c 100644
--- a/code-generation/language/java/src/main/java/org/apache/plc4x/codegeneration/language/java/generators/ComplexTypeGenerator.java
+++ b/code-generation/language/java/src/main/java/org/apache/plc4x/codegeneration/language/java/generators/ComplexTypeGenerator.java
@@ -85,6 +85,15 @@ public class ComplexTypeGenerator extends BaseGenerator<ComplexTypeDefinition> {
                     }
                 }
 
+                // Keep the discriminators no sub type pins, so a sub type whose case leaves one
+                // open can read back what was parsed instead of returning a zero.
+                for (String discriminatorName : retainedDiscriminators(complexTypeDefinition)) {
+                    complexTypeBuilder.addField(FieldSpec.builder(toTypeName(discriminatorMap.get(discriminatorName), true), discriminatorName)
+                        .addModifiers(Modifier.PROTECTED)
+                        .addJavadoc("Discriminator field $L, as it was read from the wire.\n", discriminatorName)
+                        .build());
+                }
+
                 complexTypeBuilder.addMethod(MethodSpec.methodBuilder("serialize" + complexTypeDefinition.getName() + "Child")
                     .addModifiers(Modifier.PROTECTED, Modifier.ABSTRACT)
                     .addParameter(WriteBuffer.class, "writeBuffer")
@@ -125,6 +134,8 @@ public class ComplexTypeGenerator extends BaseGenerator<ComplexTypeDefinition> {
                         } else {
                             methodSpec.addStatement("return ($T) $L", typeName, toParseExpression(complexTypeDefinition, null, discriminatorType, discriminatorValueTerm, parserArguments));
                         }
+                    } else if (retainedDiscriminators(parentType).contains(discriminatorName)) {
+                        methodSpec.addStatement("return this.$L", discriminatorName);
                     } else {
                         methodSpec.addStatement("return $L", getNullValueForTypeReference(discriminatorType));
                     }
@@ -482,7 +493,20 @@ public class ComplexTypeGenerator extends BaseGenerator<ComplexTypeDefinition> {
             // - It's both a parent and a child -> create BuilderImpl instance and pass in builder
             // - It's neither a parent nor a child -> create current type directly
             if (complexTypeDefinition.isDiscriminatedParentTypeDefinition() && !complexTypeDefinition.isDiscriminatedChildTypeDefinition()) {
-                codeBlocksParse.add(CodeBlock.of("return builder.build($L);", CodeBlock.join(constructorArgCodeBlocks, ", ")));
+                List<String> retained = retainedDiscriminators(complexTypeDefinition);
+                if (retained.isEmpty()) {
+                    codeBlocksParse.add(CodeBlock.of("return builder.build($L);", CodeBlock.join(constructorArgCodeBlocks, ", ")));
+                } else {
+                    CodeBlock.Builder retainBlock = CodeBlock.builder();
+                    retainBlock.addStatement("$T _instance = builder.build($L)", ClassName.get(targetPackage, complexTypeDefinition.getName()), CodeBlock.join(constructorArgCodeBlocks, ", "));
+                    for (String discriminatorName : retained) {
+                        retainBlock.beginControlFlow("if (_retained$L != null)", StaticHelper.CAPITALIZE(discriminatorName));
+                        retainBlock.addStatement("_instance.$L = _retained$L", discriminatorName, StaticHelper.CAPITALIZE(discriminatorName));
+                        retainBlock.endControlFlow();
+                    }
+                    retainBlock.addStatement("return _instance");
+                    codeBlocksParse.add(retainBlock.build());
+                }
             } else if (complexTypeDefinition.isDiscriminatedChildTypeDefinition() && !complexTypeDefinition.isDiscriminatedParentTypeDefinition()) {
                 codeBlocksParse.add(CodeBlock.of("return new $T($L);", ClassName.get("", complexTypeDefinition.getParentType().orElseThrow().getName() + "BuilderImpl"), CodeBlock.join(constructorArgCodeBlocks, ", ")));
             } else if (complexTypeDefinition.isDiscriminatedChildTypeDefinition() && complexTypeDefinition.isDiscriminatedParentTypeDefinition()) {
@@ -659,6 +683,14 @@ public class ComplexTypeGenerator extends BaseGenerator<ComplexTypeDefinition> {
             expressionTypeNames.add(getLanguageTypeNameForTypeReference(typeReference, true));
             expressionParameters.add(toParseExpression(typeDefinition, switchField, typeReference, discriminatorExpression, parserArguments));
         }
+        ComplexTypeDefinition switchOwner = typeDefinition.asComplexTypeDefinition().orElseThrow();
+        Map<String, TypeReference> switchDiscriminatorTypes = getDiscriminatorTypes(switchOwner);
+        for (String discriminatorName : retainedDiscriminators(switchOwner)) {
+            // Boxed, so that only the cases that leave this discriminator open assign it.
+            parseBlockBuilder.addStatement("$T _retained$L = null",
+                toTypeName(switchDiscriminatorTypes.get(discriminatorName), true).box(),
+                StaticHelper.CAPITALIZE(discriminatorName));
+        }
         parseBlockBuilder.addStatement("$T builder = null", ClassName.get("", typeDefinition.getName() + "Builder"));
         CodeBlock.Builder switchBlock = CodeBlock.builder();
         boolean elseAdded = false;
@@ -700,6 +732,9 @@ public class ComplexTypeGenerator extends BaseGenerator<ComplexTypeDefinition> {
                 aCase.getAllParserArguments().orElseThrow().forEach(argument -> staticParseArgs.add(", $L", argument.getName()));
             }
             switchBlock.addStatement("builder = $T.staticParse$L(readBuffer$L)", ClassName.get(targetPackage, aCase.getName()), CodeBlock.of(typeDefinition.getName() + "Builder"), staticParseArgs.build());
+            for (String discriminatorName : retainedDiscriminatorsForCase(switchOwner, aCase)) {
+                switchBlock.addStatement("_retained$L = $L", StaticHelper.CAPITALIZE(discriminatorName), discriminatorName);
+            }
         }
         // If a switch field doesn't contain a single case, we need to skip the final closing bracket.
         if (!switchBlock.isEmpty()) {
@@ -746,4 +781,56 @@ public class ComplexTypeGenerator extends BaseGenerator<ComplexTypeDefinition> {
         }
         return getLanguageTypeNameForTypeReference(typeReference, allowPrimitive);
     }
+
+    /**
+     * The discriminators a parent has to remember after parsing.
+     *
+     * <p>A sub type's discriminator getter is normally a constant, because the typeSwitch case pins
+     * the value. A case that leaves a discriminator open, by not mentioning it or by matching it with
+     * a wildcard, gives the sub type nothing to return. The parent therefore keeps the value it
+     * parsed and the sub type reads it back from there; otherwise the value is silently replaced by
+     * a zero, both when asking the sub type for it and when serializing the parent again.
+     *
+     * <p>Only discriminators that are fields of the parent qualify. Parser arguments are supplied by
+     * the caller instead of being read from the wire and are never serialized, and an implicit field
+     * is recomputed on serialization, so neither needs to be retained.
+     *
+     * <p>A type that is both a parent and a child hands a builder to its own parent rather than
+     * constructing an instance here, so it has nowhere to apply this. No such type needs it today.
+     */
+    protected List<String> retainedDiscriminators(ComplexTypeDefinition typeDefinition) {
+        if (!typeDefinition.isDiscriminatedParentTypeDefinition() || typeDefinition.getSwitchField().isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<String> retained = new ArrayList<>();
+        for (DiscriminatedComplexTypeDefinition aCase : typeDefinition.getSwitchField().orElseThrow().getCases()) {
+            for (String discriminatorName : retainedDiscriminatorsForCase(typeDefinition, aCase)) {
+                if (!retained.contains(discriminatorName)) {
+                    retained.add(discriminatorName);
+                }
+            }
+        }
+        return retained;
+    }
+
+    /**
+     * The retained discriminators one particular typeSwitch case leaves open. Assigning only these,
+     * in the case that needs them, keeps the parent free of state no sub type ever reads.
+     */
+    protected List<String> retainedDiscriminatorsForCase(ComplexTypeDefinition typeDefinition, DiscriminatedComplexTypeDefinition aCase) {
+        List<String> retained = new ArrayList<>();
+        for (Map.Entry<String, Term> discriminator : aCase.getDiscriminatorMap().entrySet()) {
+            Term term = discriminator.getValue();
+            if ((term != null) && !(term instanceof WildcardTerm)) {
+                continue;
+            }
+            String discriminatorName = discriminator.getKey();
+            if (typeDefinition.getNamedFieldByName(discriminatorName)
+                .filter(NamedField::isDiscriminatorField).isEmpty()) {
+                continue;
+            }
+            retained.add(discriminatorName);
+        }
+        return retained;
+    }
 }
diff --git a/plc4j/drivers/ads/src/main/generated/org/apache/plc4x/java/ads/readwrite/AdsErrorResponse.java b/plc4j/drivers/ads/src/main/generated/org/apache/plc4x/java/ads/readwrite/AdsErrorResponse.java
index 422b268136..c71cabc5a7 100644
--- a/plc4j/drivers/ads/src/main/generated/org/apache/plc4x/java/ads/readwrite/AdsErrorResponse.java
+++ b/plc4j/drivers/ads/src/main/generated/org/apache/plc4x/java/ads/readwrite/AdsErrorResponse.java
@@ -37,7 +37,7 @@ public class AdsErrorResponse extends AmsPacket implements Message {
    */
   @Override
   public CommandId getCommandId() {
-    return null;
+    return this.commandId;
   }
 
   /**
@@ -45,7 +45,7 @@ public class AdsErrorResponse extends AmsPacket implements Message {
    */
   @Override
   public boolean getResponse() {
-    return false;
+    return this.response;
   }
 
   public static AmsPacketBuilder staticParseAmsPacketBuilder(ReadBuffer readBuffer) throws
diff --git a/plc4j/drivers/ads/src/main/generated/org/apache/plc4x/java/ads/readwrite/AmsPacket.java b/plc4j/drivers/ads/src/main/generated/org/apache/plc4x/java/ads/readwrite/AmsPacket.java
index 3e8657c9d1..a93b2d9283 100644
--- a/plc4j/drivers/ads/src/main/generated/org/apache/plc4x/java/ads/readwrite/AmsPacket.java
+++ b/plc4j/drivers/ads/src/main/generated/org/apache/plc4x/java/ads/readwrite/AmsPacket.java
@@ -48,6 +48,16 @@ public abstract class AmsPacket implements Message {
 
   public static final Boolean BROADCAST = false;
 
+  /**
+   * Discriminator field commandId, as it was read from the wire.
+   */
+  protected CommandId commandId;
+
+  /**
+   * Discriminator field response, as it was read from the wire.
+   */
+  protected boolean response;
+
   protected final AmsNetId targetAmsNetId;
 
   protected final int targetAmsPort;
@@ -239,6 +249,8 @@ public abstract class AmsPacket implements Message {
     long invokeId = FieldReaderFactory.readSimpleField(DataReaderFactory.readUnsignedLong(readBuffer, 32), WithOption.WithName("invokeId"));
 
     // Switch Field
+    CommandId _retainedCommandId = null;
+    Boolean _retainedResponse = null;
     AmsPacketBuilder builder = null;
     if (EvaluationHelper.equals(errorCode, (ReturnCode) (ReturnCode.OK)) && EvaluationHelper.equals(commandId, (CommandId) (CommandId.INVALID)) && EvaluationHelper.equals(response, (boolean) (false))) {
       builder = AdsInvalidRequest.staticParseAmsPacketBuilder(readBuffer);
@@ -282,13 +294,22 @@ public abstract class AmsPacket implements Message {
       builder = AdsReadWriteResponse.staticParseAmsPacketBuilder(readBuffer);
     } else {
       builder = AdsErrorResponse.staticParseAmsPacketBuilder(readBuffer);
+      _retainedCommandId = commandId;
+      _retainedResponse = response;
     }
     if (builder == null) {
       throw new BufferException("Unsupported case for discriminated type parameters parameters [errorCode, commandId, response]");
     }
 
     readBuffer.popContext();
-    return builder.build(targetAmsNetId, targetAmsPort, sourceAmsNetId, sourceAmsPort, errorCode, invokeId);
+    AmsPacket _instance = builder.build(targetAmsNetId, targetAmsPort, sourceAmsNetId, sourceAmsPort, errorCode, invokeId);
+    if (_retainedCommandId != null) {
+      _instance.commandId = _retainedCommandId;
+    }
+    if (_retainedResponse != null) {
+      _instance.response = _retainedResponse;
+    }
+    return _instance;
   }
 
   @Override
diff --git a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/APDU.java b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/APDU.java
index 4d42b783e0..825f86aedf 100644
--- a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/APDU.java
+++ b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/APDU.java
@@ -32,6 +32,11 @@ import org.apache.plc4x.java.spi.fields.utils.ThreadLocalHelper;
  * Code generated by code-generation. DO NOT EDIT.
  */
 public abstract class APDU implements Message {
+  /**
+   * Discriminator field apduType, as it was read from the wire.
+   */
+  protected ApduType apduType;
+
   public APDU() {
   }
 
@@ -50,6 +55,7 @@ public abstract class APDU implements Message {
     ApduType apduType = FieldReaderFactory.readDiscriminatorEnumField(DataReaderFactory.readEnum(ApduType::enumForValue, DataReaderFactory.readUnsignedByte(readBuffer, 4)), WithOption.WithName("apduType"));
 
     // Switch Field
+    ApduType _retainedApduType = null;
     APDUBuilder builder = null;
     if (EvaluationHelper.equals(apduType, (ApduType) (ApduType.CONFIRMED_REQUEST_PDU))) {
       builder = APDUConfirmedRequest.staticParseAPDUBuilder(readBuffer, apduLength);
@@ -69,13 +75,18 @@ public abstract class APDU implements Message {
       builder = APDUAbort.staticParseAPDUBuilder(readBuffer, apduLength);
     } else if (true) {
       builder = APDUUnknown.staticParseAPDUBuilder(readBuffer, apduLength);
+      _retainedApduType = apduType;
     }
     if (builder == null) {
       throw new BufferException("Unsupported case for discriminated type parameters parameters [apduType]");
     }
 
     readBuffer.popContext();
-    return builder.build();
+    APDU _instance = builder.build();
+    if (_retainedApduType != null) {
+      _instance.apduType = _retainedApduType;
+    }
+    return _instance;
   }
 
   @Override
diff --git a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/APDUUnknown.java b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/APDUUnknown.java
index 157b722c60..31edc1326b 100644
--- a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/APDUUnknown.java
+++ b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/APDUUnknown.java
@@ -45,7 +45,7 @@ public class APDUUnknown extends APDU implements Message {
    */
   @Override
   public ApduType getApduType() {
-    return null;
+    return this.apduType;
   }
 
   /**
diff --git a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetConfirmedServiceRequest.java b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetConfirmedServiceRequest.java
index e8fe4fac54..a7a4433971 100644
--- a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetConfirmedServiceRequest.java
+++ b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetConfirmedServiceRequest.java
@@ -32,6 +32,11 @@ import org.apache.plc4x.java.spi.fields.utils.ThreadLocalHelper;
  * Code generated by code-generation. DO NOT EDIT.
  */
 public abstract class BACnetConfirmedServiceRequest implements Message {
+  /**
+   * Discriminator field serviceChoice, as it was read from the wire.
+   */
+  protected BACnetConfirmedServiceChoice serviceChoice;
+
   protected final long serviceRequestLength;
 
   public BACnetConfirmedServiceRequest(Long serviceRequestLength) {
@@ -72,6 +77,7 @@ public abstract class BACnetConfirmedServiceRequest implements Message {
     long serviceRequestPayloadLength = FieldReaderFactory.readVirtualField(long.class, ((((serviceRequestLength) > (0))) ? ((serviceRequestLength) - (1L)) : 0L), WithOption.WithName("serviceRequestPayloadLength"));
 
     // Switch Field
+    BACnetConfirmedServiceChoice _retainedServiceChoice = null;
     BACnetConfirmedServiceRequestBuilder builder = null;
     if (EvaluationHelper.equals(serviceChoice, (BACnetConfirmedServiceChoice) (BACnetConfirmedServiceChoice.ACKNOWLEDGE_ALARM))) {
       builder = BACnetConfirmedServiceRequestAcknowledgeAlarm.staticParseBACnetConfirmedServiceRequestBuilder(readBuffer, serviceRequestLength);
@@ -137,13 +143,18 @@ public abstract class BACnetConfirmedServiceRequest implements Message {
       builder = BACnetConfirmedServiceRequestReadPropertyConditional.staticParseBACnetConfirmedServiceRequestBuilder(readBuffer, serviceRequestPayloadLength, serviceRequestLength);
     } else if (true) {
       builder = BACnetConfirmedServiceRequestUnknown.staticParseBACnetConfirmedServiceRequestBuilder(readBuffer, serviceRequestPayloadLength, serviceRequestLength);
+      _retainedServiceChoice = serviceChoice;
     }
     if (builder == null) {
       throw new BufferException("Unsupported case for discriminated type parameters parameters [serviceChoice]");
     }
 
     readBuffer.popContext();
-    return builder.build(serviceRequestLength);
+    BACnetConfirmedServiceRequest _instance = builder.build(serviceRequestLength);
+    if (_retainedServiceChoice != null) {
+      _instance.serviceChoice = _retainedServiceChoice;
+    }
+    return _instance;
   }
 
   @Override
diff --git a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetConfirmedServiceRequestUnknown.java b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetConfirmedServiceRequestUnknown.java
index 9eed332ece..b3f4bb4194 100644
--- a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetConfirmedServiceRequestUnknown.java
+++ b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetConfirmedServiceRequestUnknown.java
@@ -41,7 +41,7 @@ public class BACnetConfirmedServiceRequestUnknown extends BACnetConfirmedService
    */
   @Override
   public BACnetConfirmedServiceChoice getServiceChoice() {
-    return null;
+    return this.serviceChoice;
   }
 
   /**
diff --git a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetUnconfirmedServiceRequest.java b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetUnconfirmedServiceRequest.java
index a6b9193ea6..b87b82c735 100644
--- a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetUnconfirmedServiceRequest.java
+++ b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetUnconfirmedServiceRequest.java
@@ -32,6 +32,11 @@ import org.apache.plc4x.java.spi.fields.utils.ThreadLocalHelper;
  * Code generated by code-generation. DO NOT EDIT.
  */
 public abstract class BACnetUnconfirmedServiceRequest implements Message {
+  /**
+   * Discriminator field serviceChoice, as it was read from the wire.
+   */
+  protected BACnetUnconfirmedServiceChoice serviceChoice;
+
   public BACnetUnconfirmedServiceRequest() {
   }
 
@@ -52,6 +57,7 @@ public abstract class BACnetUnconfirmedServiceRequest implements Message {
     BACnetUnconfirmedServiceChoice serviceChoice = FieldReaderFactory.readDiscriminatorEnumField(DataReaderFactory.readEnum(BACnetUnconfirmedServiceChoice::enumForValue, DataReaderFactory.readUnsignedShort(readBuffer, 8)), WithOption.WithName("serviceChoice"));
 
     // Switch Field
+    BACnetUnconfirmedServiceChoice _retainedServiceChoice = null;
     BACnetUnconfirmedServiceRequestBuilder builder = null;
     if (EvaluationHelper.equals(serviceChoice, (BACnetUnconfirmedServiceChoice) (BACnetUnconfirmedServiceChoice.I_AM))) {
       builder = BACnetUnconfirmedServiceRequestIAm.staticParseBACnetUnconfirmedServiceRequestBuilder(readBuffer, serviceRequestLength);
@@ -79,13 +85,18 @@ public abstract class BACnetUnconfirmedServiceRequest implements Message {
       builder = BACnetUnconfirmedServiceRequestUnconfirmedCOVNotificationMultiple.staticParseBACnetUnconfirmedServiceRequestBuilder(readBuffer, serviceRequestLength);
     } else if (true) {
       builder = BACnetUnconfirmedServiceRequestUnknown.staticParseBACnetUnconfirmedServiceRequestBuilder(readBuffer, serviceRequestLength);
+      _retainedServiceChoice = serviceChoice;
     }
     if (builder == null) {
       throw new BufferException("Unsupported case for discriminated type parameters parameters [serviceChoice]");
     }
 
     readBuffer.popContext();
-    return builder.build();
+    BACnetUnconfirmedServiceRequest _instance = builder.build();
+    if (_retainedServiceChoice != null) {
+      _instance.serviceChoice = _retainedServiceChoice;
+    }
+    return _instance;
   }
 
   @Override
diff --git a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetUnconfirmedServiceRequestUnknown.java b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetUnconfirmedServiceRequestUnknown.java
index 8cdbe6b63e..6d0f37a7ef 100644
--- a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetUnconfirmedServiceRequestUnknown.java
+++ b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetUnconfirmedServiceRequestUnknown.java
@@ -40,7 +40,7 @@ public class BACnetUnconfirmedServiceRequestUnknown extends BACnetUnconfirmedSer
    */
   @Override
   public BACnetUnconfirmedServiceChoice getServiceChoice() {
-    return null;
+    return this.serviceChoice;
   }
 
   /**
diff --git a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/NLM.java b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/NLM.java
index 31ca1209cb..12f84daf0c 100644
--- a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/NLM.java
+++ b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/NLM.java
@@ -32,6 +32,11 @@ import org.apache.plc4x.java.spi.fields.utils.ThreadLocalHelper;
  * Code generated by code-generation. DO NOT EDIT.
  */
 public abstract class NLM implements Message {
+  /**
+   * Discriminator field messageType, as it was read from the wire.
+   */
+  protected short messageType;
+
   public NLM() {
   }
 
@@ -60,6 +65,7 @@ public abstract class NLM implements Message {
     boolean isVendorProprietaryMessage = FieldReaderFactory.readVirtualField(boolean.class, (messageType) >= (128), WithOption.WithName("isVendorProprietaryMessage"));
 
     // Switch Field
+    Short _retainedMessageType = null;
     NLMBuilder builder = null;
     if (EvaluationHelper.equals(messageType, (short) (0x00))) {
       builder = NLMWhoIsRouterToNetwork.staticParseNLMBuilder(readBuffer, apduLength);
@@ -103,15 +109,21 @@ public abstract class NLM implements Message {
       builder = NLMNetworkNumberIs.staticParseNLMBuilder(readBuffer, apduLength);
     } else if (true && EvaluationHelper.equals(isVendorProprietaryMessage, (boolean) (false))) {
       builder = NLMReserved.staticParseNLMBuilder(readBuffer, apduLength);
+      _retainedMessageType = messageType;
     } else if (true) {
       builder = NLMVendorProprietaryMessage.staticParseNLMBuilder(readBuffer, apduLength);
+      _retainedMessageType = messageType;
     }
     if (builder == null) {
       throw new BufferException("Unsupported case for discriminated type parameters parameters [messageType, isVendorProprietaryMessage]");
     }
 
     readBuffer.popContext();
-    return builder.build();
+    NLM _instance = builder.build();
+    if (_retainedMessageType != null) {
+      _instance.messageType = _retainedMessageType;
+    }
+    return _instance;
   }
 
   @Override
diff --git a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/NLMReserved.java b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/NLMReserved.java
index 5b0235942d..6766c2af52 100644
--- a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/NLMReserved.java
+++ b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/NLMReserved.java
@@ -40,7 +40,7 @@ public class NLMReserved extends NLM implements Message {
    */
   @Override
   public short getMessageType() {
-    return 0;
+    return this.messageType;
   }
 
   /**
diff --git a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/NLMVendorProprietaryMessage.java b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/NLMVendorProprietaryMessage.java
index 913800e322..bdfd2591cb 100644
--- a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/NLMVendorProprietaryMessage.java
+++ b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/NLMVendorProprietaryMessage.java
@@ -45,7 +45,7 @@ public class NLMVendorProprietaryMessage extends NLM implements Message {
    */
   @Override
   public short getMessageType() {
-    return 0;
+    return this.messageType;
   }
 
   /**
diff --git a/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/LDataFrame.java b/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/LDataFrame.java
index 841f9f1edb..d9dc81d99a 100644
--- a/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/LDataFrame.java
+++ b/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/LDataFrame.java
@@ -32,6 +32,11 @@ import org.apache.plc4x.java.spi.fields.utils.ThreadLocalHelper;
  * Code generated by code-generation. DO NOT EDIT.
  */
 public abstract class LDataFrame implements Message {
+  /**
+   * Discriminator field polling, as it was read from the wire.
+   */
+  protected boolean polling;
+
   protected final boolean frameType;
 
   protected final boolean notRepeated;
@@ -124,6 +129,7 @@ public abstract class LDataFrame implements Message {
     boolean errorFlag = FieldReaderFactory.readSimpleField(DataReaderFactory.readBoolean(readBuffer), WithOption.WithName("errorFlag"));
 
     // Switch Field
+    Boolean _retainedPolling = null;
     LDataFrameBuilder builder = null;
     if (EvaluationHelper.equals(notAckFrame, (boolean) (true)) && EvaluationHelper.equals(polling, (boolean) (false))) {
       builder = LDataExtended.staticParseLDataFrameBuilder(readBuffer);
@@ -131,13 +137,18 @@ public abstract class LDataFrame implements Message {
       builder = LPollData.staticParseLDataFrameBuilder(readBuffer);
     } else if (EvaluationHelper.equals(notAckFrame, (boolean) (false))) {
       builder = LDataFrameACK.staticParseLDataFrameBuilder(readBuffer);
+      _retainedPolling = polling;
     }
     if (builder == null) {
       throw new BufferException("Unsupported case for discriminated type parameters parameters [notAckFrame, polling]");
     }
 
     readBuffer.popContext();
-    return builder.build(frameType, notRepeated, priority, acknowledgeRequested, errorFlag);
+    LDataFrame _instance = builder.build(frameType, notRepeated, priority, acknowledgeRequested, errorFlag);
+    if (_retainedPolling != null) {
+      _instance.polling = _retainedPolling;
+    }
+    return _instance;
   }
 
   @Override
diff --git a/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/LDataFrameACK.java b/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/LDataFrameACK.java
index 2e62b9b48d..bc2deebaf6 100644
--- a/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/LDataFrameACK.java
+++ b/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/LDataFrameACK.java
@@ -45,7 +45,7 @@ public class LDataFrameACK extends LDataFrame implements Message {
    */
   @Override
   public boolean getPolling() {
-    return false;
+    return this.polling;
   }
 
   public static LDataFrameBuilder staticParseLDataFrameBuilder(ReadBuffer readBuffer) throws
diff --git a/plc4j/drivers/modbus/src/main/generated/org/apache/plc4x/java/modbus/readwrite/ModbusPDU.java b/plc4j/drivers/modbus/src/main/generated/org/apache/plc4x/java/modbus/readwrite/ModbusPDU.java
index 7f650f5d47..eac740353e 100644
--- a/plc4j/drivers/modbus/src/main/generated/org/apache/plc4x/java/modbus/readwrite/ModbusPDU.java
+++ b/plc4j/drivers/modbus/src/main/generated/org/apache/plc4x/java/modbus/readwrite/ModbusPDU.java
@@ -32,6 +32,11 @@ import org.apache.plc4x.java.spi.fields.utils.ThreadLocalHelper;
  * Code generated by code-generation. DO NOT EDIT.
  */
 public abstract class ModbusPDU implements Message {
+  /**
+   * Discriminator field functionFlag, as it was read from the wire.
+   */
+  protected byte functionFlag;
+
   public ModbusPDU() {
   }
 
@@ -64,9 +69,11 @@ public abstract class ModbusPDU implements Message {
     byte functionFlag = FieldReaderFactory.readDiscriminatorField(DataReaderFactory.readUnsignedByte(readBuffer, 7), WithOption.WithName("functionFlag"));
 
     // Switch Field
+    Byte _retainedFunctionFlag = null;
     ModbusPDUBuilder builder = null;
     if (EvaluationHelper.equals(errorFlag, (boolean) (true))) {
       builder = ModbusPDUError.staticParseModbusPDUBuilder(readBuffer, response);
+      _retainedFunctionFlag = functionFlag;
     } else if (EvaluationHelper.equals(errorFlag, (boolean) (false)) && EvaluationHelper.equals(functionFlag, (byte) (0x02)) && EvaluationHelper.equals(response, (boolean) (false))) {
       builder = ModbusPDUReadDiscreteInputsRequest.staticParseModbusPDUBuilder(readBuffer, response);
     } else if (EvaluationHelper.equals(errorFlag, (boolean) (false)) && EvaluationHelper.equals(functionFlag, (byte) (0x02)) && EvaluationHelper.equals(response, (boolean) (true))) {
@@ -149,7 +156,11 @@ public abstract class ModbusPDU implements Message {
     }
 
     readBuffer.popContext();
-    return builder.build();
+    ModbusPDU _instance = builder.build();
+    if (_retainedFunctionFlag != null) {
+      _instance.functionFlag = _retainedFunctionFlag;
+    }
+    return _instance;
   }
 
   @Override
diff --git a/plc4j/drivers/modbus/src/main/generated/org/apache/plc4x/java/modbus/readwrite/ModbusPDUError.java b/plc4j/drivers/modbus/src/main/generated/org/apache/plc4x/java/modbus/readwrite/ModbusPDUError.java
index f32da60a65..9c85a5a720 100644
--- a/plc4j/drivers/modbus/src/main/generated/org/apache/plc4x/java/modbus/readwrite/ModbusPDUError.java
+++ b/plc4j/drivers/modbus/src/main/generated/org/apache/plc4x/java/modbus/readwrite/ModbusPDUError.java
@@ -50,7 +50,7 @@ public class ModbusPDUError extends ModbusPDU implements Message {
    */
   @Override
   public byte getFunctionFlag() {
-    return 0;
+    return this.functionFlag;
   }
 
   /**
diff --git a/plc4j/drivers/umas/src/main/generated/org/apache/plc4x/java/umas/readwrite/ModbusPDU.java b/plc4j/drivers/umas/src/main/generated/org/apache/plc4x/java/umas/readwrite/ModbusPDU.java
index dda59f45ec..5aaf4bba82 100644
--- a/plc4j/drivers/umas/src/main/generated/org/apache/plc4x/java/umas/readwrite/ModbusPDU.java
+++ b/plc4j/drivers/umas/src/main/generated/org/apache/plc4x/java/umas/readwrite/ModbusPDU.java
@@ -32,6 +32,11 @@ import org.apache.plc4x.java.spi.fields.utils.ThreadLocalHelper;
  * Code generated by code-generation. DO NOT EDIT.
  */
 public abstract class ModbusPDU implements Message {
+  /**
+   * Discriminator field functionFlag, as it was read from the wire.
+   */
+  protected byte functionFlag;
+
   public ModbusPDU() {
   }
 
@@ -59,9 +64,11 @@ public abstract class ModbusPDU implements Message {
     byte functionFlag = FieldReaderFactory.readDiscriminatorField(DataReaderFactory.readUnsignedByte(readBuffer, 7), WithOption.WithName("functionFlag"));
 
     // Switch Field
+    Byte _retainedFunctionFlag = null;
     ModbusPDUBuilder builder = null;
     if (EvaluationHelper.equals(errorFlag, (boolean) (true))) {
       builder = ModbusPDUError.staticParseModbusPDUBuilder(readBuffer, umasRequestFunctionKey, byteLength);
+      _retainedFunctionFlag = functionFlag;
     } else if (EvaluationHelper.equals(errorFlag, (boolean) (false)) && EvaluationHelper.equals(functionFlag, (byte) (0x5a))) {
       builder = UmasPDU.staticParseModbusPDUBuilder(readBuffer, umasRequestFunctionKey, byteLength);
     }
@@ -70,7 +77,11 @@ public abstract class ModbusPDU implements Message {
     }
 
     readBuffer.popContext();
-    return builder.build();
+    ModbusPDU _instance = builder.build();
+    if (_retainedFunctionFlag != null) {
+      _instance.functionFlag = _retainedFunctionFlag;
+    }
+    return _instance;
   }
 
   @Override
diff --git a/plc4j/drivers/umas/src/main/generated/org/apache/plc4x/java/umas/readwrite/ModbusPDUError.java b/plc4j/drivers/umas/src/main/generated/org/apache/plc4x/java/umas/readwrite/ModbusPDUError.java
index 59f6f3b420..eee4f21a7a 100644
--- a/plc4j/drivers/umas/src/main/generated/org/apache/plc4x/java/umas/readwrite/ModbusPDUError.java
+++ b/plc4j/drivers/umas/src/main/generated/org/apache/plc4x/java/umas/readwrite/ModbusPDUError.java
@@ -50,7 +50,7 @@ public class ModbusPDUError extends ModbusPDU implements Message {
    */
   @Override
   public byte getFunctionFlag() {
-    return 0;
+    return this.functionFlag;
   }
 
   /**