From fdfeb0a02057b08364ccee114221e15e59a88580 Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Thu, 22 May 2025 14:03:51 -0700 Subject: [PATCH 01/15] Test files --- .../main/compat-avro/under110/IntsAndLongs.avsc | 15 +++++++++++++++ .../main/compat-avro/under111/IntsAndLongs.avsc | 15 +++++++++++++++ .../main/compat-avro/under14/IntsAndLongs.avsc | 15 +++++++++++++++ .../main/compat-avro/under15/IntsAndLongs.avsc | 15 +++++++++++++++ .../main/compat-avro/under16/IntsAndLongs.avsc | 15 +++++++++++++++ .../main/compat-avro/under17/IntsAndLongs.avsc | 15 +++++++++++++++ .../main/compat-avro/under18/IntsAndLongs.avsc | 15 +++++++++++++++ .../main/compat-avro/under19/IntsAndLongs.avsc | 15 +++++++++++++++ 8 files changed, 120 insertions(+) create mode 100644 helper/tests/codegen-110/src/main/compat-avro/under110/IntsAndLongs.avsc create mode 100644 helper/tests/codegen-111/src/main/compat-avro/under111/IntsAndLongs.avsc create mode 100644 helper/tests/codegen-14/src/main/compat-avro/under14/IntsAndLongs.avsc create mode 100644 helper/tests/codegen-15/src/main/compat-avro/under15/IntsAndLongs.avsc create mode 100644 helper/tests/codegen-16/src/main/compat-avro/under16/IntsAndLongs.avsc create mode 100644 helper/tests/codegen-17/src/main/compat-avro/under17/IntsAndLongs.avsc create mode 100644 helper/tests/codegen-18/src/main/compat-avro/under18/IntsAndLongs.avsc create mode 100644 helper/tests/codegen-19/src/main/compat-avro/under19/IntsAndLongs.avsc diff --git a/helper/tests/codegen-110/src/main/compat-avro/under110/IntsAndLongs.avsc b/helper/tests/codegen-110/src/main/compat-avro/under110/IntsAndLongs.avsc new file mode 100644 index 000000000..8577684e3 --- /dev/null +++ b/helper/tests/codegen-110/src/main/compat-avro/under110/IntsAndLongs.avsc @@ -0,0 +1,15 @@ +{ + "type": "record", + "namespace": "under110", + "name": "IntsAndLongs", + "fields": [ + { + "name": "longField", + "type": "long" + }, + { + "name": "intField", + "type": "int" + } + ] +} \ No newline at end of file diff --git a/helper/tests/codegen-111/src/main/compat-avro/under111/IntsAndLongs.avsc b/helper/tests/codegen-111/src/main/compat-avro/under111/IntsAndLongs.avsc new file mode 100644 index 000000000..cd8e6672b --- /dev/null +++ b/helper/tests/codegen-111/src/main/compat-avro/under111/IntsAndLongs.avsc @@ -0,0 +1,15 @@ +{ + "type": "record", + "namespace": "under111", + "name": "IntsAndLongs", + "fields": [ + { + "name": "longField", + "type": "long" + }, + { + "name": "intField", + "type": "int" + } + ] +} diff --git a/helper/tests/codegen-14/src/main/compat-avro/under14/IntsAndLongs.avsc b/helper/tests/codegen-14/src/main/compat-avro/under14/IntsAndLongs.avsc new file mode 100644 index 000000000..3a446559c --- /dev/null +++ b/helper/tests/codegen-14/src/main/compat-avro/under14/IntsAndLongs.avsc @@ -0,0 +1,15 @@ +{ + "type": "record", + "namespace": "under14", + "name": "IntsAndLongs", + "fields": [ + { + "name": "longField", + "type": "long" + }, + { + "name": "intField", + "type": "int" + } + ] +} diff --git a/helper/tests/codegen-15/src/main/compat-avro/under15/IntsAndLongs.avsc b/helper/tests/codegen-15/src/main/compat-avro/under15/IntsAndLongs.avsc new file mode 100644 index 000000000..328ef705b --- /dev/null +++ b/helper/tests/codegen-15/src/main/compat-avro/under15/IntsAndLongs.avsc @@ -0,0 +1,15 @@ +{ + "type": "record", + "namespace": "under15", + "name": "IntsAndLongs", + "fields": [ + { + "name": "longField", + "type": "long" + }, + { + "name": "intField", + "type": "int" + } + ] +} diff --git a/helper/tests/codegen-16/src/main/compat-avro/under16/IntsAndLongs.avsc b/helper/tests/codegen-16/src/main/compat-avro/under16/IntsAndLongs.avsc new file mode 100644 index 000000000..d6c9833cf --- /dev/null +++ b/helper/tests/codegen-16/src/main/compat-avro/under16/IntsAndLongs.avsc @@ -0,0 +1,15 @@ +{ + "type": "record", + "namespace": "under16", + "name": "IntsAndLongs", + "fields": [ + { + "name": "longField", + "type": "long" + }, + { + "name": "intField", + "type": "int" + } + ] +} diff --git a/helper/tests/codegen-17/src/main/compat-avro/under17/IntsAndLongs.avsc b/helper/tests/codegen-17/src/main/compat-avro/under17/IntsAndLongs.avsc new file mode 100644 index 000000000..1df2af22b --- /dev/null +++ b/helper/tests/codegen-17/src/main/compat-avro/under17/IntsAndLongs.avsc @@ -0,0 +1,15 @@ +{ + "type": "record", + "namespace": "under17", + "name": "IntsAndLongs", + "fields": [ + { + "name": "longField", + "type": "long" + }, + { + "name": "intField", + "type": "int" + } + ] +} diff --git a/helper/tests/codegen-18/src/main/compat-avro/under18/IntsAndLongs.avsc b/helper/tests/codegen-18/src/main/compat-avro/under18/IntsAndLongs.avsc new file mode 100644 index 000000000..1456139b7 --- /dev/null +++ b/helper/tests/codegen-18/src/main/compat-avro/under18/IntsAndLongs.avsc @@ -0,0 +1,15 @@ +{ + "type": "record", + "namespace": "under18", + "name": "IntsAndLongs", + "fields": [ + { + "name": "longField", + "type": "long" + }, + { + "name": "intField", + "type": "int" + } + ] +} diff --git a/helper/tests/codegen-19/src/main/compat-avro/under19/IntsAndLongs.avsc b/helper/tests/codegen-19/src/main/compat-avro/under19/IntsAndLongs.avsc new file mode 100644 index 000000000..321a0f778 --- /dev/null +++ b/helper/tests/codegen-19/src/main/compat-avro/under19/IntsAndLongs.avsc @@ -0,0 +1,15 @@ +{ + "type": "record", + "namespace": "under19", + "name": "IntsAndLongs", + "fields": [ + { + "name": "longField", + "type": "long" + }, + { + "name": "intField", + "type": "int" + } + ] +} From 3f93744adafe9decdde0fce472f9ecd0b406536e Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Thu, 22 May 2025 14:16:30 -0700 Subject: [PATCH 02/15] put and set --- .../compatibility/CodeTransformations.java | 163 +++++++++++++++++- 1 file changed, 161 insertions(+), 2 deletions(-) diff --git a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java index 2e2f04593..0b8ebfe3b 100644 --- a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java +++ b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java @@ -20,7 +20,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; - /** * utility class for transforming avro-generated java code */ @@ -131,6 +130,10 @@ public static String applyAll( fixed = CodeTransformations.transformParseCalls(fixed, generatedBy, minSupportedVersion, maxSupportedVersion, alternativeAvsc, importStatements); fixed = CodeTransformations.addGetClassSchemaMethod(fixed, generatedBy, minSupportedVersion, maxSupportedVersion); + // Allow int fields to be set using longs, and vice versa + fixed = CodeTransformations.enhanceNumericPutMethod(fixed); + fixed = CodeTransformations.enhanceNumericSetterMethods(fixed); + //1.6+ features if (minSupportedVersion.earlierThan(AvroVersion.AVRO_1_6)) { //optionally strip out builders @@ -519,7 +522,7 @@ public static String removeBuilderSupport(String code, AvroVersion minSupportedV //find the end of the inner builder class Matcher endBuilderMatcher = END_BUILDER_CLASS_PATTERN.matcher(code); - if (!endBuilderMatcher.find(builderMethodMatcher.end())) { + if (!endBuilderMatcher.find(buildMethodMatcher.end())) { throw new IllegalStateException("cant locate builder support block in " + code); } @@ -919,6 +922,162 @@ private static String fixBuilderConstructors(String code) { " " + BUILDER_INSTANCE_NAME + " = new " + builderClassName + "(false);\n" + code.substring(insertPosition); } + /** + * Transforms the put method in Avro-generated record classes to allow setting Long values into int fields + * and Integer values into long fields. This improves type compatibility when working with numeric fields. + * + * @param code generated code + * @return transformed code with enhanced put method + */ + public static String enhanceNumericPutMethod(String code) { + if (code == null || code.isEmpty()) { + return code; + } + + // Pattern to match the put method with simple casts + Pattern putMethodPattern = Pattern.compile( + "public\\s+void\\s+put\\s*\\(\\s*int\\s+field\\$\\s*,\\s*java\\.lang\\.Object\\s+value\\$\\s*\\)\\s*\\{\\s*" + + "switch\\s*\\(\\s*field\\$\\s*\\)\\s*\\{\\s*" + + "([^}]+)" + // Capture the case statements + "\\}\\s*\\}" + ); + + Matcher matcher = putMethodPattern.matcher(code); + if (!matcher.find()) { + return code; // No matching put method found + } + + String caseStatements = matcher.group(1); + + // Pattern to find int and long field cases + Pattern casePattern = Pattern.compile("case\\s+(\\d+)\\s*:\\s*(\\w+)\\s*=\\s*\\(([^)]+)\\)value\\$\\s*;\\s*break\\s*;"); + + Matcher caseMatcher = casePattern.matcher(caseStatements); + + Map fieldInfo = new HashMap<>(); // caseNumber -> [fieldName, fieldType] + + while (caseMatcher.find()) { + String caseNumber = caseMatcher.group(1); + String fieldName = caseMatcher.group(2); + String fieldType = caseMatcher.group(3); + fieldInfo.put(caseNumber, new String[]{fieldName, fieldType}); + } + + // Build the enhanced put method + StringBuilder enhancedPutMethod = new StringBuilder(); + enhancedPutMethod.append("public void put(int field$, java.lang.Object value$) {\n"); + enhancedPutMethod.append(" switch (field$) {\n"); + + for (Map.Entry entry : fieldInfo.entrySet()) { + String caseNumber = entry.getKey(); + String fieldName = entry.getValue()[0]; + String fieldType = entry.getValue()[1]; + + enhancedPutMethod.append(" case ").append(caseNumber).append(": "); + + if ("java.lang.Integer".equals(fieldType)) { + enhancedPutMethod.append("if (value$ instanceof java.lang.Long) {\n"); + enhancedPutMethod.append(" ").append(fieldName).append(" = ((java.lang.Long)value$).intValue();\n"); + enhancedPutMethod.append(" } else {\n"); + enhancedPutMethod.append(" ").append(fieldName).append(" = (java.lang.Integer)value$;\n"); + enhancedPutMethod.append(" }\n"); + enhancedPutMethod.append(" break;\n"); + } else if ("java.lang.Long".equals(fieldType)) { + enhancedPutMethod.append("if (value$ instanceof java.lang.Integer) {\n"); + enhancedPutMethod.append(" ").append(fieldName).append(" = ((java.lang.Integer)value$).longValue();\n"); + enhancedPutMethod.append(" } else {\n"); + enhancedPutMethod.append(" ").append(fieldName).append(" = (java.lang.Long)value$;\n"); + enhancedPutMethod.append(" }\n"); + enhancedPutMethod.append(" break;\n"); + } else { + // Keep the original behavior for other types + enhancedPutMethod.append(fieldName).append(" = (").append(fieldType).append(")value$; break;\n"); + } + } + + // Add the default case + enhancedPutMethod.append(" default: throw new IndexOutOfBoundsException(\"Invalid index: \" + field$);\n"); + enhancedPutMethod.append(" }\n}"); + + // Replace the original put method with the enhanced one + return code.substring(0, matcher.start()) + enhancedPutMethod + code.substring(matcher.end()); + } + + /** + * Enhances setter methods for int and long fields to allow cross-type assignments. + * This allows int fields to be set using long values and long fields to be set using int values. + * For int fields, a runtime check ensures the long value fits within the int range. + * + * @param code generated code + * @return transformed code with enhanced setter methods + */ + public static String enhanceNumericSetterMethods(String code) { + if (code == null || code.isEmpty()) { + return code; + } + + // First, identify all int and long fields in the class + Map fieldTypes = new HashMap<>(); // fieldName -> type + + // Pattern to match field declarations + Pattern fieldPattern = Pattern.compile("private\\s+(int|long)\\s+(\\w+)\\s*;"); + Matcher fieldMatcher = fieldPattern.matcher(code); + + while (fieldMatcher.find()) { + String fieldType = fieldMatcher.group(1); + String fieldName = fieldMatcher.group(2); + fieldTypes.put(fieldName, fieldType); + } + + if (fieldTypes.isEmpty()) { + return code; // No int or long fields found + } + + // For each field, find and enhance its setter method + for (Map.Entry entry : fieldTypes.entrySet()) { + String fieldName = entry.getKey(); + String fieldType = entry.getValue(); + + // Capitalize first letter of field name for method name + String capitalizedFieldName = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); + String setterName = "set" + capitalizedFieldName; + + // Pattern to match the setter method + Pattern setterPattern = Pattern.compile( + "public\\s+void\\s+" + setterName + "\\s*\\(\\s*" + fieldType + "\\s+\\w+\\s*\\)\\s*\\{[^}]+\\}" + ); + + Matcher setterMatcher = setterPattern.matcher(code); + + if (setterMatcher.find()) { + String originalSetter = setterMatcher.group(0); + String overloadedSetter; + + if ("int".equals(fieldType)) { + // Create an overloaded setter that accepts long for int field + overloadedSetter = + "public void " + setterName + "(long value) {\n" + + " if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n" + + " this." + fieldName + " = (int) value;\n" + + " } else {\n" + + " throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to int\");\n" + + " }\n" + + " }"; + } else { // long field + // Create an overloaded setter that accepts int for long field + overloadedSetter = + "public void " + setterName + "(int value) {\n" + + " this." + fieldName + " = value;\n" + + " }"; + } + + // Add the overloaded setter after the original setter + code = code.replace(originalSetter, originalSetter + "\n\n " + overloadedSetter); + } + } + return code; + } + private static String addImports(String code, Collection importStatements) { if (importStatements == null || importStatements.isEmpty()) { return code; From 95351ee7ef605d8efcf9aafc3efaa109cb2fd74d Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Mon, 2 Jun 2025 22:52:23 -0700 Subject: [PATCH 03/15] avsc --- .../src/main/compat-avro/under110/IntsAndLongs.avsc | 10 ++++++++++ .../src/main/compat-avro/under111/IntsAndLongs.avsc | 10 ++++++++++ .../src/main/compat-avro/under14/IntsAndLongs.avsc | 10 ++++++++++ .../src/main/compat-avro/under15/IntsAndLongs.avsc | 10 ++++++++++ .../src/main/compat-avro/under16/IntsAndLongs.avsc | 10 ++++++++++ .../src/main/compat-avro/under17/IntsAndLongs.avsc | 10 ++++++++++ .../src/main/compat-avro/under18/IntsAndLongs.avsc | 10 ++++++++++ .../src/main/compat-avro/under19/IntsAndLongs.avsc | 10 ++++++++++ 8 files changed, 80 insertions(+) diff --git a/helper/tests/codegen-110/src/main/compat-avro/under110/IntsAndLongs.avsc b/helper/tests/codegen-110/src/main/compat-avro/under110/IntsAndLongs.avsc index 8577684e3..50fa51cce 100644 --- a/helper/tests/codegen-110/src/main/compat-avro/under110/IntsAndLongs.avsc +++ b/helper/tests/codegen-110/src/main/compat-avro/under110/IntsAndLongs.avsc @@ -10,6 +10,16 @@ { "name": "intField", "type": "int" + }, + { + "name": "boxedLongField", + "type": ["null", "long"], + "default": null + }, + { + "name": "boxedIntField", + "type": ["null", "int"], + "default": null } ] } \ No newline at end of file diff --git a/helper/tests/codegen-111/src/main/compat-avro/under111/IntsAndLongs.avsc b/helper/tests/codegen-111/src/main/compat-avro/under111/IntsAndLongs.avsc index cd8e6672b..22ed16555 100644 --- a/helper/tests/codegen-111/src/main/compat-avro/under111/IntsAndLongs.avsc +++ b/helper/tests/codegen-111/src/main/compat-avro/under111/IntsAndLongs.avsc @@ -10,6 +10,16 @@ { "name": "intField", "type": "int" + }, + { + "name": "boxedLongField", + "type": ["null", "long"], + "default": null + }, + { + "name": "boxedIntField", + "type": ["null", "int"], + "default": null } ] } diff --git a/helper/tests/codegen-14/src/main/compat-avro/under14/IntsAndLongs.avsc b/helper/tests/codegen-14/src/main/compat-avro/under14/IntsAndLongs.avsc index 3a446559c..da42ec88c 100644 --- a/helper/tests/codegen-14/src/main/compat-avro/under14/IntsAndLongs.avsc +++ b/helper/tests/codegen-14/src/main/compat-avro/under14/IntsAndLongs.avsc @@ -10,6 +10,16 @@ { "name": "intField", "type": "int" + }, + { + "name": "boxedLongField", + "type": ["null", "long"], + "default": null + }, + { + "name": "boxedIntField", + "type": ["null", "int"], + "default": null } ] } diff --git a/helper/tests/codegen-15/src/main/compat-avro/under15/IntsAndLongs.avsc b/helper/tests/codegen-15/src/main/compat-avro/under15/IntsAndLongs.avsc index 328ef705b..7101398f7 100644 --- a/helper/tests/codegen-15/src/main/compat-avro/under15/IntsAndLongs.avsc +++ b/helper/tests/codegen-15/src/main/compat-avro/under15/IntsAndLongs.avsc @@ -10,6 +10,16 @@ { "name": "intField", "type": "int" + }, + { + "name": "boxedLongField", + "type": ["null", "long"], + "default": null + }, + { + "name": "boxedIntField", + "type": ["null", "int"], + "default": null } ] } diff --git a/helper/tests/codegen-16/src/main/compat-avro/under16/IntsAndLongs.avsc b/helper/tests/codegen-16/src/main/compat-avro/under16/IntsAndLongs.avsc index d6c9833cf..4c5243b68 100644 --- a/helper/tests/codegen-16/src/main/compat-avro/under16/IntsAndLongs.avsc +++ b/helper/tests/codegen-16/src/main/compat-avro/under16/IntsAndLongs.avsc @@ -10,6 +10,16 @@ { "name": "intField", "type": "int" + }, + { + "name": "boxedLongField", + "type": ["null", "long"], + "default": null + }, + { + "name": "boxedIntField", + "type": ["null", "int"], + "default": null } ] } diff --git a/helper/tests/codegen-17/src/main/compat-avro/under17/IntsAndLongs.avsc b/helper/tests/codegen-17/src/main/compat-avro/under17/IntsAndLongs.avsc index 1df2af22b..3782dd70d 100644 --- a/helper/tests/codegen-17/src/main/compat-avro/under17/IntsAndLongs.avsc +++ b/helper/tests/codegen-17/src/main/compat-avro/under17/IntsAndLongs.avsc @@ -10,6 +10,16 @@ { "name": "intField", "type": "int" + }, + { + "name": "boxedLongField", + "type": ["null", "long"], + "default": null + }, + { + "name": "boxedIntField", + "type": ["null", "int"], + "default": null } ] } diff --git a/helper/tests/codegen-18/src/main/compat-avro/under18/IntsAndLongs.avsc b/helper/tests/codegen-18/src/main/compat-avro/under18/IntsAndLongs.avsc index 1456139b7..c4b301b1c 100644 --- a/helper/tests/codegen-18/src/main/compat-avro/under18/IntsAndLongs.avsc +++ b/helper/tests/codegen-18/src/main/compat-avro/under18/IntsAndLongs.avsc @@ -10,6 +10,16 @@ { "name": "intField", "type": "int" + }, + { + "name": "boxedLongField", + "type": ["null", "long"], + "default": null + }, + { + "name": "boxedIntField", + "type": ["null", "int"], + "default": null } ] } diff --git a/helper/tests/codegen-19/src/main/compat-avro/under19/IntsAndLongs.avsc b/helper/tests/codegen-19/src/main/compat-avro/under19/IntsAndLongs.avsc index 321a0f778..68b6be6ec 100644 --- a/helper/tests/codegen-19/src/main/compat-avro/under19/IntsAndLongs.avsc +++ b/helper/tests/codegen-19/src/main/compat-avro/under19/IntsAndLongs.avsc @@ -10,6 +10,16 @@ { "name": "intField", "type": "int" + }, + { + "name": "boxedLongField", + "type": ["null", "long"], + "default": null + }, + { + "name": "boxedIntField", + "type": ["null", "int"], + "default": null } ] } From c6921bcc6619b7949c02c1c7a14d0053c3e23960 Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Tue, 3 Jun 2025 10:35:16 -0700 Subject: [PATCH 04/15] setters --- .../compatibility/CodeTransformations.java | 169 +++++++++++++----- 1 file changed, 122 insertions(+), 47 deletions(-) diff --git a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java index 0b8ebfe3b..41a80720f 100644 --- a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java +++ b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java @@ -132,7 +132,7 @@ public static String applyAll( // Allow int fields to be set using longs, and vice versa fixed = CodeTransformations.enhanceNumericPutMethod(fixed); - fixed = CodeTransformations.enhanceNumericSetterMethods(fixed); + fixed = CodeTransformations.addOverloadedNumericSetterMethods(fixed); //1.6+ features if (minSupportedVersion.earlierThan(AvroVersion.AVRO_1_6)) { @@ -951,7 +951,6 @@ public static String enhanceNumericPutMethod(String code) { // Pattern to find int and long field cases Pattern casePattern = Pattern.compile("case\\s+(\\d+)\\s*:\\s*(\\w+)\\s*=\\s*\\(([^)]+)\\)value\\$\\s*;\\s*break\\s*;"); - Matcher caseMatcher = casePattern.matcher(caseStatements); Map fieldInfo = new HashMap<>(); // caseNumber -> [fieldName, fieldType] @@ -977,16 +976,38 @@ public static String enhanceNumericPutMethod(String code) { if ("java.lang.Integer".equals(fieldType)) { enhancedPutMethod.append("if (value$ instanceof java.lang.Long) {\n"); - enhancedPutMethod.append(" ").append(fieldName).append(" = ((java.lang.Long)value$).intValue();\n"); + enhancedPutMethod.append(" if ((java.lang.Long)value$ <= Integer.MAX_VALUE && (java.lang.Long)value$ >= Integer.MIN_VALUE) {\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = (int) value$;\n"); + enhancedPutMethod.append(" } else {\n"); + enhancedPutMethod.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value$ + \" cannot be cast to int\");\n"); + enhancedPutMethod.append(" }\n"); + enhancedPutMethod.append(" } else {\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = (java.lang.Integer)value$;\n"); + enhancedPutMethod.append(" }\n"); + enhancedPutMethod.append(" break;\n"); + } else if ("int".equals(fieldType)) { + enhancedPutMethod.append("if (value$ instanceof java.lang.Long) {\n"); + enhancedPutMethod.append(" if ((java.lang.Long)value$ <= Integer.MAX_VALUE && (java.lang.Long)value$ >= Integer.MIN_VALUE) {\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = (int) value$;\n"); + enhancedPutMethod.append(" } else {\n"); + enhancedPutMethod.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value$ + \" cannot be cast to int\");\n"); + enhancedPutMethod.append(" }\n"); enhancedPutMethod.append(" } else {\n"); - enhancedPutMethod.append(" ").append(fieldName).append(" = (java.lang.Integer)value$;\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = (int)value$;\n"); enhancedPutMethod.append(" }\n"); enhancedPutMethod.append(" break;\n"); } else if ("java.lang.Long".equals(fieldType)) { enhancedPutMethod.append("if (value$ instanceof java.lang.Integer) {\n"); - enhancedPutMethod.append(" ").append(fieldName).append(" = ((java.lang.Integer)value$).longValue();\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = ((java.lang.Integer)value$).longValue();\n"); + enhancedPutMethod.append(" } else {\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = (java.lang.Long)value$;\n"); + enhancedPutMethod.append(" }\n"); + enhancedPutMethod.append(" break;\n"); + } else if ("long".equals(fieldType)) { + enhancedPutMethod.append("if (value$ instanceof java.lang.Integer) {\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = ((java.lang.Integer)value$).longValue();\n"); enhancedPutMethod.append(" } else {\n"); - enhancedPutMethod.append(" ").append(fieldName).append(" = (java.lang.Long)value$;\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = (long)value$;\n"); enhancedPutMethod.append(" }\n"); enhancedPutMethod.append(" break;\n"); } else { @@ -999,83 +1020,137 @@ public static String enhanceNumericPutMethod(String code) { enhancedPutMethod.append(" default: throw new IndexOutOfBoundsException(\"Invalid index: \" + field$);\n"); enhancedPutMethod.append(" }\n}"); - // Replace the original put method with the enhanced one + // Replace the original put method with the enhanced version return code.substring(0, matcher.start()) + enhancedPutMethod + code.substring(matcher.end()); } /** - * Enhances setter methods for int and long fields to allow cross-type assignments. - * This allows int fields to be set using long values and long fields to be set using int values. - * For int fields, a runtime check ensures the long value fits within the int range. + * Enhances setter methods for numeric types (int, long, Integer, Long) to allow cross-type assignments. + * This allows int/Integer fields to be set using long/Long values and long/Long fields to be set using int/Integer values. + * For int/Integer fields, a runtime check ensures the long/Long value fits within the int/Integer range. * * @param code generated code * @return transformed code with enhanced setter methods */ - public static String enhanceNumericSetterMethods(String code) { + public static String addOverloadedNumericSetterMethods(String code) { if (code == null || code.isEmpty()) { return code; } - // First, identify all int and long fields in the class + // First, identify all numeric fields in the class (both primitive and boxed) Map fieldTypes = new HashMap<>(); // fieldName -> type - // Pattern to match field declarations - Pattern fieldPattern = Pattern.compile("private\\s+(int|long)\\s+(\\w+)\\s*;"); - Matcher fieldMatcher = fieldPattern.matcher(code); + // Pattern to match primitive field declarations + Pattern primitiveFieldPattern = Pattern.compile("private\\s+(int|long)\\s+(\\w+)\\s*;"); + Matcher primitiveFieldMatcher = primitiveFieldPattern.matcher(code); - while (fieldMatcher.find()) { - String fieldType = fieldMatcher.group(1); - String fieldName = fieldMatcher.group(2); + while (primitiveFieldMatcher.find()) { + String fieldType = primitiveFieldMatcher.group(1); + String fieldName = primitiveFieldMatcher.group(2); + fieldTypes.put(fieldName, fieldType); + } + + // Pattern to match boxed field declarations + Pattern boxedFieldPattern = Pattern.compile("private\\s+(java\\.lang\\.Integer|java\\.lang\\.Long)\\s+(\\w+)\\s*;"); + Matcher boxedFieldMatcher = boxedFieldPattern.matcher(code); + + while (boxedFieldMatcher.find()) { + String fieldType = boxedFieldMatcher.group(1); + String fieldName = boxedFieldMatcher.group(2); fieldTypes.put(fieldName, fieldType); } if (fieldTypes.isEmpty()) { - return code; // No int or long fields found + return code; // No numeric fields found } + StringBuilder result = new StringBuilder(code); + // For each field, find and enhance its setter method for (Map.Entry entry : fieldTypes.entrySet()) { String fieldName = entry.getKey(); String fieldType = entry.getValue(); - // Capitalize first letter of field name for method name - String capitalizedFieldName = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); - String setterName = "set" + capitalizedFieldName; + // Convert field name to method name (camelCase to PascalCase) + String methodName = "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); - // Pattern to match the setter method - Pattern setterPattern = Pattern.compile( - "public\\s+void\\s+" + setterName + "\\s*\\(\\s*" + fieldType + "\\s+\\w+\\s*\\)\\s*\\{[^}]+\\}" - ); + // Pattern to match the setter method - handle both primitive and boxed types + Pattern setterPattern; + if ("int".equals(fieldType) || "long".equals(fieldType)) { + setterPattern = Pattern.compile( + "public\\s+void\\s+" + methodName + "\\s*\\(\\s*" + fieldType + "\\s+\\w+\\s*\\)\\s*\\{[^}]+\\}" + ); + } else { + setterPattern = Pattern.compile( + "public\\s+void\\s+" + methodName + "\\s*\\(\\s*" + fieldType + "\\s+value\\s*\\)\\s*\\{\\s*" + + "this\\." + fieldName + "\\s*=\\s*value;\\s*" + + "\\}" + ); + } - Matcher setterMatcher = setterPattern.matcher(code); + Matcher setterMatcher = setterPattern.matcher(result); if (setterMatcher.find()) { - String originalSetter = setterMatcher.group(0); - String overloadedSetter; + StringBuilder enhancedSetter = new StringBuilder(); if ("int".equals(fieldType)) { - // Create an overloaded setter that accepts long for int field - overloadedSetter = - "public void " + setterName + "(long value) {\n" + - " if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n" + - " this." + fieldName + " = (int) value;\n" + - " } else {\n" + - " throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to int\");\n" + - " }\n" + - " }"; - } else { // long field - // Create an overloaded setter that accepts int for long field - overloadedSetter = - "public void " + setterName + "(int value) {\n" + - " this." + fieldName + " = value;\n" + - " }"; + // For int fields, add an overloaded setter that accepts long with bounds checking + enhancedSetter.append(" public void ").append(methodName).append("(long value) {\n"); + enhancedSetter.append(" if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); + enhancedSetter.append(" this.").append(fieldName).append(" = (int) value;\n"); + enhancedSetter.append(" } else {\n"); + enhancedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to int\");\n"); + enhancedSetter.append(" }\n"); + enhancedSetter.append(" }\n\n"); + + enhancedSetter.append(" public void ").append(methodName).append("(int value) {\n"); + enhancedSetter.append(" this.").append(fieldName).append(" = value;\n"); + enhancedSetter.append(" }\n\n"); + } else if ("long".equals(fieldType)) { + // For long fields, add an overloaded setter that accepts int + enhancedSetter.append(" public void ").append(methodName).append("(long value) {\n"); + enhancedSetter.append(" this.").append(fieldName).append(" = value;\n"); + enhancedSetter.append(" }\n\n"); + + enhancedSetter.append(" public void ").append(methodName).append("(int value) {\n"); + enhancedSetter.append(" this.").append(fieldName).append(" = value;\n"); + enhancedSetter.append(" }\n\n"); + } else if ("java.lang.Integer".equals(fieldType)) { + // For Integer fields, add an overloaded setter that accepts Long with bounds checking + enhancedSetter.append(" public void ").append(methodName).append("(java.lang.Integer value) {\n"); + enhancedSetter.append(" this.").append(fieldName).append(" = value;\n"); + enhancedSetter.append(" }\n\n"); + + enhancedSetter.append(" public void ").append(methodName).append("(java.lang.Long value) {\n"); + enhancedSetter.append(" if (value == null) {\n"); + enhancedSetter.append(" this.").append(fieldName).append(" = null;\n"); + enhancedSetter.append(" } else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); + enhancedSetter.append(" this.").append(fieldName).append(" = value.intValue();\n"); + enhancedSetter.append(" } else {\n"); + enhancedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to Integer\");\n"); + enhancedSetter.append(" }\n"); + enhancedSetter.append(" }\n\n"); + } else { // java.lang.Long + // For Long fields, add an overloaded setter that accepts Integer + enhancedSetter.append(" public void ").append(methodName).append("(java.lang.Long value) {\n"); + enhancedSetter.append(" this.").append(fieldName).append(" = value;\n"); + enhancedSetter.append(" }\n\n"); + + enhancedSetter.append(" public void ").append(methodName).append("(java.lang.Integer value) {\n"); + enhancedSetter.append(" if (value == null) {\n"); + enhancedSetter.append(" this.").append(fieldName).append(" = null;\n"); + enhancedSetter.append(" } else {\n"); + enhancedSetter.append(" this.").append(fieldName).append(" = value.longValue();\n"); + enhancedSetter.append(" }\n"); + enhancedSetter.append(" }\n\n"); } - // Add the overloaded setter after the original setter - code = code.replace(originalSetter, originalSetter + "\n\n " + overloadedSetter); + // Replace the original setter with the enhanced version + result.replace(setterMatcher.start(), setterMatcher.end(), enhancedSetter.toString()); } } - return code; + + return result.toString(); } private static String addImports(String code, Collection importStatements) { From 7c046ba527752cfca8a8531708848692488a615e Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Tue, 3 Jun 2025 11:27:08 -0700 Subject: [PATCH 05/15] constructor --- .../compatibility/CodeTransformations.java | 247 ++++++++++++++++++ 1 file changed, 247 insertions(+) diff --git a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java index 41a80720f..5e0ce6bce 100644 --- a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java +++ b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java @@ -133,6 +133,7 @@ public static String applyAll( // Allow int fields to be set using longs, and vice versa fixed = CodeTransformations.enhanceNumericPutMethod(fixed); fixed = CodeTransformations.addOverloadedNumericSetterMethods(fixed); + fixed = CodeTransformations.addOverloadedNumericConstructor(fixed); //1.6+ features if (minSupportedVersion.earlierThan(AvroVersion.AVRO_1_6)) { @@ -1153,6 +1154,252 @@ public static String addOverloadedNumericSetterMethods(String code) { return result.toString(); } + /** + * Adds an overloaded constructor that allows Integer parameters for Long fields and Long parameters for Integer fields. + * This makes the generated code more flexible by allowing automatic type conversion between numeric types. + * The implementation handles both boxed Integer/Long types and primitive int/long types, with proper null handling + * for boxed types and default values for primitives when null is passed. + * + * @param code generated code + * @return transformed code with overloaded constructor for numeric type conversions + */ + public static String addOverloadedNumericConstructor(String code) { + if (code == null || code.isEmpty()) { + return code; + } + + // First, identify all numeric fields in the class (both primitive and boxed) + Map fieldTypes = new HashMap<>(); // fieldName -> type + + // Pattern to match primitive field declarations + Pattern primitiveFieldPattern = Pattern.compile("private\\s+(int|long)\\s+(\\w+)\\s*;"); + Matcher primitiveFieldMatcher = primitiveFieldPattern.matcher(code); + + while (primitiveFieldMatcher.find()) { + String fieldType = primitiveFieldMatcher.group(1); + String fieldName = primitiveFieldMatcher.group(2); + fieldTypes.put(fieldName, fieldType); + } + + // Pattern to match boxed field declarations + Pattern boxedFieldPattern = Pattern.compile("private\\s+(java\\.lang\\.Integer|java\\.lang\\.Long)\\s+(\\w+)\\s*;"); + Matcher boxedFieldMatcher = boxedFieldPattern.matcher(code); + + while (boxedFieldMatcher.find()) { + String fieldType = boxedFieldMatcher.group(1); + String fieldName = boxedFieldMatcher.group(2); + fieldTypes.put(fieldName, fieldType); + } + + if (fieldTypes.isEmpty()) { + return code; // No numeric fields found + } + + // Find the class name + Pattern classNamePattern = Pattern.compile("public\\s+class\\s+(\\w+)"); + Matcher classNameMatcher = classNamePattern.matcher(code); + if (!classNameMatcher.find()) { + return code; // Can't find class name + } + String className = classNameMatcher.group(1); + + // Find the all-args constructor + Pattern constructorPattern = Pattern.compile( + "public\\s+" + className + "\\s*\\(([^)]*)\\)\\s*\\{([^}]*)\\}" + ); + Matcher constructorMatcher = constructorPattern.matcher(code); + + // Skip the default constructor (no args) and find the all-args constructor + if (constructorMatcher.find() && constructorMatcher.group(1).trim().isEmpty()) { + if (!constructorMatcher.find()) { + return code; // Can't find all-args constructor + } + } + + String constructorParams = constructorMatcher.group(1); + + // Parse the constructor parameters using a more robust approach that handles generic types + List paramTypes = new ArrayList<>(); + List paramNames = new ArrayList<>(); + Map originalParamTypes = new HashMap<>(); // paramName -> original type + + // Parse parameters more carefully to handle generics + parseConstructorParameters(constructorParams, paramTypes, paramNames, originalParamTypes); + + // Generate the overloaded constructor with swapped numeric types + StringBuilder overloadedConstructor = new StringBuilder(); + overloadedConstructor.append("\n /**\n"); + overloadedConstructor.append(" * All-args constructor with flexible numeric type conversion.\n"); + overloadedConstructor.append(" * Allows Integer parameters for Long fields and Long parameters for Integer fields.\n"); + + // Add parameter javadoc + for (int i = 0; i < paramNames.size(); i++) { + overloadedConstructor.append(" * @param ").append(paramNames.get(i)).append(" The new value for ").append(paramNames.get(i)).append("\n"); + } + + overloadedConstructor.append(" */\n"); + overloadedConstructor.append(" public ").append(className).append("("); + + // Generate parameters with swapped types for numeric fields + Map swappedParamTypes = new HashMap<>(); // paramName -> swapped type + + for (int i = 0; i < paramTypes.size(); i++) { + if (i > 0) { + overloadedConstructor.append(", "); + } + + String paramType = paramTypes.get(i); + String paramName = paramNames.get(i); + String fieldType = fieldTypes.get(paramName); + + // Swap Java types for numeric fields + if ("java.lang.Long".equals(paramType) && fieldTypes.containsKey(paramName)) { + overloadedConstructor.append("java.lang.Integer ").append(paramName); + swappedParamTypes.put(paramName, "java.lang.Integer"); + } else if ("java.lang.Integer".equals(paramType) && fieldTypes.containsKey(paramName)) { + overloadedConstructor.append("java.lang.Long ").append(paramName); + swappedParamTypes.put(paramName, "java.lang.Long"); + } else { + overloadedConstructor.append(paramType).append(" ").append(paramName); + swappedParamTypes.put(paramName, paramType); + } + } + + overloadedConstructor.append(") {\n"); + + // Generate constructor body with type conversion logic + for (int i = 0; i < paramNames.size(); i++) { + String paramName = paramNames.get(i); + String fieldType = fieldTypes.get(paramName); + String swappedParamType = swappedParamTypes.get(paramName); + + if (fieldType != null) { + if ("int".equals(fieldType) && "java.lang.Long".equals(swappedParamType)) { + // Convert Long to int with bounds check + overloadedConstructor.append(" if (").append(paramName).append(" == null) {\n"); + overloadedConstructor.append(" this.").append(paramName).append(" = 0;\n"); + overloadedConstructor.append(" } else if (").append(paramName).append(" <= Integer.MAX_VALUE && ") + .append(paramName).append(" >= Integer.MIN_VALUE) {\n"); + overloadedConstructor.append(" this.").append(paramName).append(" = ").append(paramName).append(".intValue();\n"); + overloadedConstructor.append(" } else {\n"); + overloadedConstructor.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + ") + .append(paramName).append(" + \" cannot be cast to int\");\n"); + overloadedConstructor.append(" }\n"); + } else if ("long".equals(fieldType) && "java.lang.Integer".equals(swappedParamType)) { + // Convert Integer to long + overloadedConstructor.append(" this.").append(paramName).append(" = ").append(paramName).append(" == null ? 0L : ") + .append(paramName).append(".longValue();\n"); + } else if ("java.lang.Integer".equals(fieldType) && "java.lang.Long".equals(swappedParamType)) { + // Convert Long to Integer with bounds check + overloadedConstructor.append(" if (").append(paramName).append(" == null) {\n"); + overloadedConstructor.append(" this.").append(paramName).append(" = null;\n"); + overloadedConstructor.append(" } else if (").append(paramName).append(" <= Integer.MAX_VALUE && ") + .append(paramName).append(" >= Integer.MIN_VALUE) {\n"); + overloadedConstructor.append(" this.").append(paramName).append(" = ").append(paramName).append(".intValue();\n"); + overloadedConstructor.append(" } else {\n"); + overloadedConstructor.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + ") + .append(paramName).append(" + \" cannot be cast to Integer\");\n"); + overloadedConstructor.append(" }\n"); + } else if ("java.lang.Long".equals(fieldType) && "java.lang.Integer".equals(swappedParamType)) { + // Convert Integer to Long + overloadedConstructor.append(" if (").append(paramName).append(" == null) {\n"); + overloadedConstructor.append(" this.").append(paramName).append(" = null;\n"); + overloadedConstructor.append(" } else {\n"); + overloadedConstructor.append(" this.").append(paramName).append(" = ").append(paramName).append(".longValue();\n"); + overloadedConstructor.append(" }\n"); + } else { + // For non-numeric fields or fields with the same type, just assign directly + overloadedConstructor.append(" this.").append(paramName).append(" = ").append(paramName).append(";\n"); + } + } else { + // For fields not in our fieldTypes map, just assign directly + overloadedConstructor.append(" this.").append(paramName).append(" = ").append(paramName).append(";\n"); + } + } + + overloadedConstructor.append(" }"); + + // Insert the overloaded constructor after the original constructor + StringBuilder result = new StringBuilder(code); + result.insert(constructorMatcher.end(), "\n" + overloadedConstructor.toString()); + + return result.toString(); + } + + /** + * Parses constructor parameters handling complex generic types correctly. + * This method properly handles nested generics like Map>. + * + * @param constructorParams The constructor parameter string + * @param paramTypes Output list to store parameter types + * @param paramNames Output list to store parameter names + * @param originalParamTypes Output map to store parameter name to type mapping + */ + private static void parseConstructorParameters(String constructorParams, + List paramTypes, + List paramNames, + Map originalParamTypes) { + if (constructorParams == null || constructorParams.trim().isEmpty()) { + return; + } + + int pos = 0; + int len = constructorParams.length(); + int angleNestLevel = 0; + int startPos = 0; + + while (pos < len) { + char c = constructorParams.charAt(pos); + + if (c == '<') { + angleNestLevel++; + } else if (c == '>') { + angleNestLevel--; + } else if (c == ',' && angleNestLevel == 0) { + // Found a top-level comma, which separates parameters + String param = constructorParams.substring(startPos, pos).trim(); + processParameter(param, paramTypes, paramNames, originalParamTypes); + startPos = pos + 1; + } + + pos++; + } + + // Process the last parameter + if (startPos < len) { + String param = constructorParams.substring(startPos).trim(); + processParameter(param, paramTypes, paramNames, originalParamTypes); + } + } + + /** + * Processes a single parameter string and extracts the type and name. + * + * @param param The parameter string (e.g., "java.lang.Integer fieldName") + * @param paramTypes Output list to store parameter types + * @param paramNames Output list to store parameter names + * @param originalParamTypes Output map to store parameter name to type mapping + */ + private static void processParameter(String param, + List paramTypes, + List paramNames, + Map originalParamTypes) { + if (param == null || param.trim().isEmpty()) { + return; + } + + // Find the last space which separates the type from the name + int lastSpacePos = param.lastIndexOf(' '); + if (lastSpacePos > 0) { + String paramType = param.substring(0, lastSpacePos).trim(); + String paramName = param.substring(lastSpacePos + 1).trim(); + + paramTypes.add(paramType); + paramNames.add(paramName); + originalParamTypes.put(paramName, paramType); + } + } + private static String addImports(String code, Collection importStatements) { if (importStatements == null || importStatements.isEmpty()) { return code; From a19af06019054f5960028aeb380534951ba5b95d Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Tue, 3 Jun 2025 14:08:11 -0700 Subject: [PATCH 06/15] fixed --- .../compatibility/CodeTransformations.java | 226 +++++++++++------- 1 file changed, 139 insertions(+), 87 deletions(-) diff --git a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java index 5e0ce6bce..b43cc2fd1 100644 --- a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java +++ b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java @@ -926,6 +926,7 @@ private static String fixBuilderConstructors(String code) { /** * Transforms the put method in Avro-generated record classes to allow setting Long values into int fields * and Integer values into long fields. This improves type compatibility when working with numeric fields. + * This method preserves existing case statements and only enhances the ones for numeric fields. * * @param code generated code * @return transformed code with enhanced put method @@ -954,74 +955,106 @@ public static String enhanceNumericPutMethod(String code) { Pattern casePattern = Pattern.compile("case\\s+(\\d+)\\s*:\\s*(\\w+)\\s*=\\s*\\(([^)]+)\\)value\\$\\s*;\\s*break\\s*;"); Matcher caseMatcher = casePattern.matcher(caseStatements); - Map fieldInfo = new HashMap<>(); // caseNumber -> [fieldName, fieldType] + // Map to store field info: caseNumber -> [fieldName, fieldType] + Map fieldInfo = new HashMap<>(); // fieldName -> type + // Find all numeric field cases while (caseMatcher.find()) { String caseNumber = caseMatcher.group(1); String fieldName = caseMatcher.group(2); String fieldType = caseMatcher.group(3); - fieldInfo.put(caseNumber, new String[]{fieldName, fieldType}); + + if ("java.lang.Integer".equals(fieldType) || "int".equals(fieldType) || + "java.lang.Long".equals(fieldType) || "long".equals(fieldType)) { + fieldInfo.put(caseNumber, new String[]{fieldName, fieldType}); + } + } + + if (fieldInfo.isEmpty()) { + return code; // No numeric fields found } - // Build the enhanced put method + // Build the enhanced put method, preserving existing case statements StringBuilder enhancedPutMethod = new StringBuilder(); enhancedPutMethod.append("public void put(int field$, java.lang.Object value$) {\n"); enhancedPutMethod.append(" switch (field$) {\n"); - for (Map.Entry entry : fieldInfo.entrySet()) { - String caseNumber = entry.getKey(); - String fieldName = entry.getValue()[0]; - String fieldType = entry.getValue()[1]; - - enhancedPutMethod.append(" case ").append(caseNumber).append(": "); - - if ("java.lang.Integer".equals(fieldType)) { - enhancedPutMethod.append("if (value$ instanceof java.lang.Long) {\n"); - enhancedPutMethod.append(" if ((java.lang.Long)value$ <= Integer.MAX_VALUE && (java.lang.Long)value$ >= Integer.MIN_VALUE) {\n"); - enhancedPutMethod.append(" this.").append(fieldName).append(" = (int) value$;\n"); - enhancedPutMethod.append(" } else {\n"); - enhancedPutMethod.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value$ + \" cannot be cast to int\");\n"); - enhancedPutMethod.append(" }\n"); - enhancedPutMethod.append(" } else {\n"); - enhancedPutMethod.append(" this.").append(fieldName).append(" = (java.lang.Integer)value$;\n"); - enhancedPutMethod.append(" }\n"); - enhancedPutMethod.append(" break;\n"); - } else if ("int".equals(fieldType)) { - enhancedPutMethod.append("if (value$ instanceof java.lang.Long) {\n"); - enhancedPutMethod.append(" if ((java.lang.Long)value$ <= Integer.MAX_VALUE && (java.lang.Long)value$ >= Integer.MIN_VALUE) {\n"); - enhancedPutMethod.append(" this.").append(fieldName).append(" = (int) value$;\n"); - enhancedPutMethod.append(" } else {\n"); - enhancedPutMethod.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value$ + \" cannot be cast to int\");\n"); - enhancedPutMethod.append(" }\n"); - enhancedPutMethod.append(" } else {\n"); - enhancedPutMethod.append(" this.").append(fieldName).append(" = (int)value$;\n"); - enhancedPutMethod.append(" }\n"); - enhancedPutMethod.append(" break;\n"); - } else if ("java.lang.Long".equals(fieldType)) { - enhancedPutMethod.append("if (value$ instanceof java.lang.Integer) {\n"); - enhancedPutMethod.append(" this.").append(fieldName).append(" = ((java.lang.Integer)value$).longValue();\n"); - enhancedPutMethod.append(" } else {\n"); - enhancedPutMethod.append(" this.").append(fieldName).append(" = (java.lang.Long)value$;\n"); - enhancedPutMethod.append(" }\n"); - enhancedPutMethod.append(" break;\n"); - } else if ("long".equals(fieldType)) { - enhancedPutMethod.append("if (value$ instanceof java.lang.Integer) {\n"); - enhancedPutMethod.append(" this.").append(fieldName).append(" = ((java.lang.Integer)value$).longValue();\n"); - enhancedPutMethod.append(" } else {\n"); - enhancedPutMethod.append(" this.").append(fieldName).append(" = (long)value$;\n"); - enhancedPutMethod.append(" }\n"); - enhancedPutMethod.append(" break;\n"); + // Reset the matcher to process all case statements + caseMatcher = Pattern.compile("case\\s+(\\d+)\\s*:([^;]*);\\s*break\\s*;").matcher(caseStatements); + int lastMatchEnd = 0; + + while (caseMatcher.find()) { + String caseNumber = caseMatcher.group(1); + + // If this is a numeric field case that we want to enhance + if (fieldInfo.containsKey(caseNumber)) { + String[] fieldData = fieldInfo.get(caseNumber); + String fieldName = fieldData[0]; + String fieldType = fieldData[1]; + + enhancedPutMethod.append(" case ").append(caseNumber).append(": "); + + if ("int".equals(fieldType)) { + enhancedPutMethod.append("if (value$ instanceof java.lang.Long) {\n"); + enhancedPutMethod.append(" if ((java.lang.Long)value$ <= Integer.MAX_VALUE && (java.lang.Long)value$ >= Integer.MIN_VALUE) {\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = ((java.lang.Long)value$).intValue();\n"); + enhancedPutMethod.append(" } else {\n"); + enhancedPutMethod.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value$ + \" cannot be cast to int\");\n"); + enhancedPutMethod.append(" }\n"); + enhancedPutMethod.append(" } else {\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = (java.lang.Integer)value$;\n"); + enhancedPutMethod.append(" }\n"); + enhancedPutMethod.append(" break;\n"); + } else if ("long".equals(fieldType)) { + enhancedPutMethod.append("if (value$ instanceof java.lang.Integer) {\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = ((java.lang.Integer)value$).longValue();\n"); + enhancedPutMethod.append(" } else {\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = (java.lang.Long)value$;\n"); + enhancedPutMethod.append(" }\n"); + enhancedPutMethod.append(" break;\n"); + } else if ("java.lang.Integer".equals(fieldType)) { + enhancedPutMethod.append("if (value$ instanceof java.lang.Long) {\n"); + enhancedPutMethod.append(" if ((java.lang.Long)value$ <= Integer.MAX_VALUE && (java.lang.Long)value$ >= Integer.MIN_VALUE) {\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = ((java.lang.Long)value$).intValue();\n"); + enhancedPutMethod.append(" } else {\n"); + enhancedPutMethod.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value$ + \" cannot be cast to Integer\");\n"); + enhancedPutMethod.append(" }\n"); + enhancedPutMethod.append(" } else {\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = (java.lang.Integer)value$;\n"); + enhancedPutMethod.append(" }\n"); + enhancedPutMethod.append(" break;\n"); + } else if ("java.lang.Long".equals(fieldType)) { + enhancedPutMethod.append("if (value$ instanceof java.lang.Integer) {\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = ((java.lang.Integer)value$).longValue();\n"); + enhancedPutMethod.append(" } else {\n"); + enhancedPutMethod.append(" this.").append(fieldName).append(" = (java.lang.Long)value$;\n"); + enhancedPutMethod.append(" }\n"); + enhancedPutMethod.append(" break;\n"); + } } else { - // Keep the original behavior for other types - enhancedPutMethod.append(fieldName).append(" = (").append(fieldType).append(")value$; break;\n"); + // For non-numeric fields, preserve the original case statement + enhancedPutMethod.append(" case ").append(caseNumber).append(":") + .append(caseMatcher.group(2)).append(";") + .append(" break;\n"); } + + lastMatchEnd = caseMatcher.end(); + } + + // Add any remaining case statements (like default case) + if (lastMatchEnd < caseStatements.length()) { + String remaining = caseStatements.substring(lastMatchEnd).trim(); + if (!remaining.isEmpty()) { + enhancedPutMethod.append(" ").append(remaining).append("\n"); + } + } else { + // Add the default case if it wasn't in the original + enhancedPutMethod.append(" default: throw new IndexOutOfBoundsException(\"Invalid index: \" + field$);\n"); } - // Add the default case - enhancedPutMethod.append(" default: throw new IndexOutOfBoundsException(\"Invalid index: \" + field$);\n"); enhancedPutMethod.append(" }\n}"); - // Replace the original put method with the enhanced version + // Replace the original put method with the enhanced one return code.substring(0, matcher.start()) + enhancedPutMethod + code.substring(matcher.end()); } @@ -1208,50 +1241,69 @@ public static String addOverloadedNumericConstructor(String code) { "public\\s+" + className + "\\s*\\(([^)]*)\\)\\s*\\{([^}]*)\\}" ); Matcher constructorMatcher = constructorPattern.matcher(code); + + // Try to find a constructor with parameters + boolean foundConstructor = false; + String constructorParams = ""; + int constructorEnd = -1; - // Skip the default constructor (no args) and find the all-args constructor - if (constructorMatcher.find() && constructorMatcher.group(1).trim().isEmpty()) { - if (!constructorMatcher.find()) { - return code; // Can't find all-args constructor + // Skip the default constructor (no args) if present + if (constructorMatcher.find()) { + String params = constructorMatcher.group(1).trim(); + if (!params.isEmpty()) { + // Found a constructor with parameters + foundConstructor = true; + constructorParams = params; + constructorEnd = constructorMatcher.end(); + } else { + // Found a default constructor, try to find another constructor with parameters + if (constructorMatcher.find()) { + foundConstructor = true; + constructorParams = constructorMatcher.group(1).trim(); + constructorEnd = constructorMatcher.end(); + } } } - - String constructorParams = constructorMatcher.group(1); + // If no constructor with parameters was found, return the original code + if (!foundConstructor || constructorParams.isEmpty()) { + return code; + } + // Parse the constructor parameters using a more robust approach that handles generic types List paramTypes = new ArrayList<>(); List paramNames = new ArrayList<>(); Map originalParamTypes = new HashMap<>(); // paramName -> original type - + // Parse parameters more carefully to handle generics parseConstructorParameters(constructorParams, paramTypes, paramNames, originalParamTypes); - + // Generate the overloaded constructor with swapped numeric types StringBuilder overloadedConstructor = new StringBuilder(); overloadedConstructor.append("\n /**\n"); overloadedConstructor.append(" * All-args constructor with flexible numeric type conversion.\n"); overloadedConstructor.append(" * Allows Integer parameters for Long fields and Long parameters for Integer fields.\n"); - + // Add parameter javadoc for (int i = 0; i < paramNames.size(); i++) { overloadedConstructor.append(" * @param ").append(paramNames.get(i)).append(" The new value for ").append(paramNames.get(i)).append("\n"); } - + overloadedConstructor.append(" */\n"); overloadedConstructor.append(" public ").append(className).append("("); - + // Generate parameters with swapped types for numeric fields Map swappedParamTypes = new HashMap<>(); // paramName -> swapped type - + for (int i = 0; i < paramTypes.size(); i++) { if (i > 0) { overloadedConstructor.append(", "); } - + String paramType = paramTypes.get(i); String paramName = paramNames.get(i); String fieldType = fieldTypes.get(paramName); - + // Swap Java types for numeric fields if ("java.lang.Long".equals(paramType) && fieldTypes.containsKey(paramName)) { overloadedConstructor.append("java.lang.Integer ").append(paramName); @@ -1264,15 +1316,15 @@ public static String addOverloadedNumericConstructor(String code) { swappedParamTypes.put(paramName, paramType); } } - + overloadedConstructor.append(") {\n"); - + // Generate constructor body with type conversion logic for (int i = 0; i < paramNames.size(); i++) { String paramName = paramNames.get(i); String fieldType = fieldTypes.get(paramName); String swappedParamType = swappedParamTypes.get(paramName); - + if (fieldType != null) { if ("int".equals(fieldType) && "java.lang.Long".equals(swappedParamType)) { // Convert Long to int with bounds check @@ -1316,16 +1368,16 @@ public static String addOverloadedNumericConstructor(String code) { overloadedConstructor.append(" this.").append(paramName).append(" = ").append(paramName).append(";\n"); } } - + overloadedConstructor.append(" }"); - + // Insert the overloaded constructor after the original constructor StringBuilder result = new StringBuilder(code); - result.insert(constructorMatcher.end(), "\n" + overloadedConstructor.toString()); - + result.insert(constructorEnd, "\n" + overloadedConstructor.toString()); + return result.toString(); } - + /** * Parses constructor parameters handling complex generic types correctly. * This method properly handles nested generics like Map>. @@ -1335,22 +1387,22 @@ public static String addOverloadedNumericConstructor(String code) { * @param paramNames Output list to store parameter names * @param originalParamTypes Output map to store parameter name to type mapping */ - private static void parseConstructorParameters(String constructorParams, - List paramTypes, - List paramNames, + private static void parseConstructorParameters(String constructorParams, + List paramTypes, + List paramNames, Map originalParamTypes) { if (constructorParams == null || constructorParams.trim().isEmpty()) { return; } - + int pos = 0; int len = constructorParams.length(); int angleNestLevel = 0; int startPos = 0; - + while (pos < len) { char c = constructorParams.charAt(pos); - + if (c == '<') { angleNestLevel++; } else if (c == '>') { @@ -1361,17 +1413,17 @@ private static void parseConstructorParameters(String constructorParams, processParameter(param, paramTypes, paramNames, originalParamTypes); startPos = pos + 1; } - + pos++; } - + // Process the last parameter if (startPos < len) { String param = constructorParams.substring(startPos).trim(); processParameter(param, paramTypes, paramNames, originalParamTypes); } } - + /** * Processes a single parameter string and extracts the type and name. * @@ -1380,20 +1432,20 @@ private static void parseConstructorParameters(String constructorParams, * @param paramNames Output list to store parameter names * @param originalParamTypes Output map to store parameter name to type mapping */ - private static void processParameter(String param, - List paramTypes, - List paramNames, + private static void processParameter(String param, + List paramTypes, + List paramNames, Map originalParamTypes) { if (param == null || param.trim().isEmpty()) { return; } - + // Find the last space which separates the type from the name int lastSpacePos = param.lastIndexOf(' '); if (lastSpacePos > 0) { String paramType = param.substring(0, lastSpacePos).trim(); String paramName = param.substring(lastSpacePos + 1).trim(); - + paramTypes.add(paramType); paramNames.add(paramName); originalParamTypes.put(paramName, paramType); From f457cda36c9c54e5d51cdff3bd93c9ab82f6d32e Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Tue, 3 Jun 2025 14:29:55 -0700 Subject: [PATCH 07/15] static --- .../compatibility/CodeTransformations.java | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java index b43cc2fd1..804efc14e 100644 --- a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java +++ b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java @@ -99,6 +99,15 @@ public class CodeTransformations { private static final String ENUM_CLASS_BODY_POST19_TEMPLATE = TemplateUtil.loadTemplate("avroutil1/templates/EnumClass19.template"); private static final String ENUM_CLASS_NO_NAMESPACE_BODY_POST19_TEMPLATE = TemplateUtil.loadTemplate("avroutil1/templates/EnumClassNoNamespace19.template"); + private static final Pattern PUT_METHOD_PATTERN = Pattern.compile( + "public\\s+void\\s+put\\s*\\(\\s*int\\s+field\\$\\s*,\\s*java\\.lang\\.Object\\s+value\\$\\s*\\)\\s*\\{\\s*" + + "switch\\s*\\(\\s*field\\$\\s*\\)\\s*\\{\\s*" + + "([^}]+)" + // Capture the case statements + "\\}\\s*\\}"); + + private static final String JAVA_LANG_INTEGER = "java.lang.Integer"; + private static final String JAVA_LANG_LONG = "java.lang.Long"; + private static final int MAX_STRING_LITERAL_SIZE = 65000; //just under 64k private CodeTransformations() { @@ -523,7 +532,7 @@ public static String removeBuilderSupport(String code, AvroVersion minSupportedV //find the end of the inner builder class Matcher endBuilderMatcher = END_BUILDER_CLASS_PATTERN.matcher(code); - if (!endBuilderMatcher.find(buildMethodMatcher.end())) { + if (!endBuilderMatcher.find(builderMethodMatcher.end())) { throw new IllegalStateException("cant locate builder support block in " + code); } @@ -937,14 +946,7 @@ public static String enhanceNumericPutMethod(String code) { } // Pattern to match the put method with simple casts - Pattern putMethodPattern = Pattern.compile( - "public\\s+void\\s+put\\s*\\(\\s*int\\s+field\\$\\s*,\\s*java\\.lang\\.Object\\s+value\\$\\s*\\)\\s*\\{\\s*" + - "switch\\s*\\(\\s*field\\$\\s*\\)\\s*\\{\\s*" + - "([^}]+)" + // Capture the case statements - "\\}\\s*\\}" - ); - - Matcher matcher = putMethodPattern.matcher(code); + Matcher matcher = PUT_METHOD_PATTERN.matcher(code); if (!matcher.find()) { return code; // No matching put method found } @@ -964,8 +966,8 @@ public static String enhanceNumericPutMethod(String code) { String fieldName = caseMatcher.group(2); String fieldType = caseMatcher.group(3); - if ("java.lang.Integer".equals(fieldType) || "int".equals(fieldType) || - "java.lang.Long".equals(fieldType) || "long".equals(fieldType)) { + if (JAVA_LANG_INTEGER.equals(fieldType) || "int".equals(fieldType) || + JAVA_LANG_LONG.equals(fieldType) || "long".equals(fieldType)) { fieldInfo.put(caseNumber, new String[]{fieldName, fieldType}); } } @@ -1149,7 +1151,7 @@ public static String addOverloadedNumericSetterMethods(String code) { enhancedSetter.append(" public void ").append(methodName).append("(int value) {\n"); enhancedSetter.append(" this.").append(fieldName).append(" = value;\n"); enhancedSetter.append(" }\n\n"); - } else if ("java.lang.Integer".equals(fieldType)) { + } else if (JAVA_LANG_INTEGER.equals(fieldType)) { // For Integer fields, add an overloaded setter that accepts Long with bounds checking enhancedSetter.append(" public void ").append(methodName).append("(java.lang.Integer value) {\n"); enhancedSetter.append(" this.").append(fieldName).append(" = value;\n"); @@ -1246,7 +1248,7 @@ public static String addOverloadedNumericConstructor(String code) { boolean foundConstructor = false; String constructorParams = ""; int constructorEnd = -1; - + // Skip the default constructor (no args) if present if (constructorMatcher.find()) { String params = constructorMatcher.group(1).trim(); @@ -1264,7 +1266,7 @@ public static String addOverloadedNumericConstructor(String code) { } } } - + // If no constructor with parameters was found, return the original code if (!foundConstructor || constructorParams.isEmpty()) { return code; @@ -1302,15 +1304,14 @@ public static String addOverloadedNumericConstructor(String code) { String paramType = paramTypes.get(i); String paramName = paramNames.get(i); - String fieldType = fieldTypes.get(paramName); // Swap Java types for numeric fields - if ("java.lang.Long".equals(paramType) && fieldTypes.containsKey(paramName)) { - overloadedConstructor.append("java.lang.Integer ").append(paramName); - swappedParamTypes.put(paramName, "java.lang.Integer"); - } else if ("java.lang.Integer".equals(paramType) && fieldTypes.containsKey(paramName)) { - overloadedConstructor.append("java.lang.Long ").append(paramName); - swappedParamTypes.put(paramName, "java.lang.Long"); + if (JAVA_LANG_LONG.equals(paramType) && fieldTypes.containsKey(paramName)) { + overloadedConstructor.append(JAVA_LANG_INTEGER + " ").append(paramName); + swappedParamTypes.put(paramName, JAVA_LANG_INTEGER); + } else if (JAVA_LANG_INTEGER.equals(paramType) && fieldTypes.containsKey(paramName)) { + overloadedConstructor.append(JAVA_LANG_LONG + " ").append(paramName); + swappedParamTypes.put(paramName, JAVA_LANG_LONG); } else { overloadedConstructor.append(paramType).append(" ").append(paramName); swappedParamTypes.put(paramName, paramType); @@ -1326,7 +1327,7 @@ public static String addOverloadedNumericConstructor(String code) { String swappedParamType = swappedParamTypes.get(paramName); if (fieldType != null) { - if ("int".equals(fieldType) && "java.lang.Long".equals(swappedParamType)) { + if ("int".equals(fieldType) && JAVA_LANG_LONG.equals(swappedParamType)) { // Convert Long to int with bounds check overloadedConstructor.append(" if (").append(paramName).append(" == null) {\n"); overloadedConstructor.append(" this.").append(paramName).append(" = 0;\n"); @@ -1337,11 +1338,11 @@ public static String addOverloadedNumericConstructor(String code) { overloadedConstructor.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + ") .append(paramName).append(" + \" cannot be cast to int\");\n"); overloadedConstructor.append(" }\n"); - } else if ("long".equals(fieldType) && "java.lang.Integer".equals(swappedParamType)) { + } else if ("long".equals(fieldType) && JAVA_LANG_INTEGER.equals(swappedParamType)) { // Convert Integer to long overloadedConstructor.append(" this.").append(paramName).append(" = ").append(paramName).append(" == null ? 0L : ") .append(paramName).append(".longValue();\n"); - } else if ("java.lang.Integer".equals(fieldType) && "java.lang.Long".equals(swappedParamType)) { + } else if (JAVA_LANG_INTEGER.equals(fieldType) && JAVA_LANG_LONG.equals(swappedParamType)) { // Convert Long to Integer with bounds check overloadedConstructor.append(" if (").append(paramName).append(" == null) {\n"); overloadedConstructor.append(" this.").append(paramName).append(" = null;\n"); @@ -1352,7 +1353,7 @@ public static String addOverloadedNumericConstructor(String code) { overloadedConstructor.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + ") .append(paramName).append(" + \" cannot be cast to Integer\");\n"); overloadedConstructor.append(" }\n"); - } else if ("java.lang.Long".equals(fieldType) && "java.lang.Integer".equals(swappedParamType)) { + } else if (JAVA_LANG_LONG.equals(fieldType) && JAVA_LANG_INTEGER.equals(swappedParamType)) { // Convert Integer to Long overloadedConstructor.append(" if (").append(paramName).append(" == null) {\n"); overloadedConstructor.append(" this.").append(paramName).append(" = null;\n"); From 7f4a97fe50709ce3f826697e240a0281ad444dde Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Wed, 4 Jun 2025 16:38:24 -0700 Subject: [PATCH 08/15] optimized setter --- .../compatibility/CodeTransformations.java | 125 ++++++++++-------- 1 file changed, 69 insertions(+), 56 deletions(-) diff --git a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java index 804efc14e..c7065a91a 100644 --- a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java +++ b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java @@ -1014,7 +1014,7 @@ public static String enhanceNumericPutMethod(String code) { enhancedPutMethod.append(" this.").append(fieldName).append(" = (java.lang.Long)value$;\n"); enhancedPutMethod.append(" }\n"); enhancedPutMethod.append(" break;\n"); - } else if ("java.lang.Integer".equals(fieldType)) { + } else if (JAVA_LANG_INTEGER.equals(fieldType)) { enhancedPutMethod.append("if (value$ instanceof java.lang.Long) {\n"); enhancedPutMethod.append(" if ((java.lang.Long)value$ <= Integer.MAX_VALUE && (java.lang.Long)value$ >= Integer.MIN_VALUE) {\n"); enhancedPutMethod.append(" this.").append(fieldName).append(" = ((java.lang.Long)value$).intValue();\n"); @@ -1025,7 +1025,7 @@ public static String enhanceNumericPutMethod(String code) { enhancedPutMethod.append(" this.").append(fieldName).append(" = (java.lang.Integer)value$;\n"); enhancedPutMethod.append(" }\n"); enhancedPutMethod.append(" break;\n"); - } else if ("java.lang.Long".equals(fieldType)) { + } else if (JAVA_LANG_LONG.equals(fieldType)) { enhancedPutMethod.append("if (value$ instanceof java.lang.Integer) {\n"); enhancedPutMethod.append(" this.").append(fieldName).append(" = ((java.lang.Integer)value$).longValue();\n"); enhancedPutMethod.append(" } else {\n"); @@ -1061,12 +1061,12 @@ public static String enhanceNumericPutMethod(String code) { } /** - * Enhances setter methods for numeric types (int, long, Integer, Long) to allow cross-type assignments. - * This allows int/Integer fields to be set using long/Long values and long/Long fields to be set using int/Integer values. - * For int/Integer fields, a runtime check ensures the long/Long value fits within the int/Integer range. + * Adds overloaded setter methods for numeric fields to allow setting Integer fields with Long values + * and Long fields with Integer values. This improves type compatibility when working with numeric fields. + * This method preserves existing setter methods and only adds the overloaded versions. * * @param code generated code - * @return transformed code with enhanced setter methods + * @return transformed code with overloaded numeric setter methods */ public static String addOverloadedNumericSetterMethods(String code) { if (code == null || code.isEmpty()) { @@ -1101,8 +1101,9 @@ public static String addOverloadedNumericSetterMethods(String code) { } StringBuilder result = new StringBuilder(code); + int insertOffset = 0; // Track the offset as we insert new methods - // For each field, find and enhance its setter method + // For each field, find its setter method and add an overloaded version for (Map.Entry entry : fieldTypes.entrySet()) { String fieldName = entry.getKey(); String fieldType = entry.getValue(); @@ -1119,70 +1120,82 @@ public static String addOverloadedNumericSetterMethods(String code) { } else { setterPattern = Pattern.compile( "public\\s+void\\s+" + methodName + "\\s*\\(\\s*" + fieldType + "\\s+value\\s*\\)\\s*\\{\\s*" + - "this\\." + fieldName + "\\s*=\\s*value;\\s*" + - "\\}" + "this\\." + fieldName + "\\s*=\\s*value;\\s*" + + "\\}" ); } Matcher setterMatcher = setterPattern.matcher(result); - if (setterMatcher.find()) { - StringBuilder enhancedSetter = new StringBuilder(); + // Find the existing setter method + if (setterMatcher.find(insertOffset)) { + int setterEnd = setterMatcher.end(); + StringBuilder overloadedSetter = new StringBuilder(); + // Add the appropriate overloaded setter based on field type if ("int".equals(fieldType)) { // For int fields, add an overloaded setter that accepts long with bounds checking - enhancedSetter.append(" public void ").append(methodName).append("(long value) {\n"); - enhancedSetter.append(" if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); - enhancedSetter.append(" this.").append(fieldName).append(" = (int) value;\n"); - enhancedSetter.append(" } else {\n"); - enhancedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to int\");\n"); - enhancedSetter.append(" }\n"); - enhancedSetter.append(" }\n\n"); - - enhancedSetter.append(" public void ").append(methodName).append("(int value) {\n"); - enhancedSetter.append(" this.").append(fieldName).append(" = value;\n"); - enhancedSetter.append(" }\n\n"); + overloadedSetter.append("\n\n /**\n"); + overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); + overloadedSetter.append(" * Accepts a long value and converts it to int with bounds checking.\n"); + overloadedSetter.append(" * @param value The long value to set\n"); + overloadedSetter.append(" * @throws org.apache.avro.AvroRuntimeException if the value is outside the int range\n"); + overloadedSetter.append(" */\n"); + overloadedSetter.append(" public void ").append(methodName).append("(long value) {\n"); + overloadedSetter.append(" if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); + overloadedSetter.append(" this.").append(fieldName).append(" = (int) value;\n"); + overloadedSetter.append(" } else {\n"); + overloadedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to int\");\n"); + overloadedSetter.append(" }\n"); + overloadedSetter.append(" }"); } else if ("long".equals(fieldType)) { // For long fields, add an overloaded setter that accepts int - enhancedSetter.append(" public void ").append(methodName).append("(long value) {\n"); - enhancedSetter.append(" this.").append(fieldName).append(" = value;\n"); - enhancedSetter.append(" }\n\n"); - - enhancedSetter.append(" public void ").append(methodName).append("(int value) {\n"); - enhancedSetter.append(" this.").append(fieldName).append(" = value;\n"); - enhancedSetter.append(" }\n\n"); + overloadedSetter.append("\n\n /**\n"); + overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); + overloadedSetter.append(" * Accepts an int value and converts it to long.\n"); + overloadedSetter.append(" * @param value The int value to set\n"); + overloadedSetter.append(" */\n"); + overloadedSetter.append(" public void ").append(methodName).append("(int value) {\n"); + overloadedSetter.append(" this.").append(fieldName).append(" = value;\n"); + overloadedSetter.append(" }"); } else if (JAVA_LANG_INTEGER.equals(fieldType)) { // For Integer fields, add an overloaded setter that accepts Long with bounds checking - enhancedSetter.append(" public void ").append(methodName).append("(java.lang.Integer value) {\n"); - enhancedSetter.append(" this.").append(fieldName).append(" = value;\n"); - enhancedSetter.append(" }\n\n"); - - enhancedSetter.append(" public void ").append(methodName).append("(java.lang.Long value) {\n"); - enhancedSetter.append(" if (value == null) {\n"); - enhancedSetter.append(" this.").append(fieldName).append(" = null;\n"); - enhancedSetter.append(" } else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); - enhancedSetter.append(" this.").append(fieldName).append(" = value.intValue();\n"); - enhancedSetter.append(" } else {\n"); - enhancedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to Integer\");\n"); - enhancedSetter.append(" }\n"); - enhancedSetter.append(" }\n\n"); - } else { // java.lang.Long + overloadedSetter.append("\n\n /**\n"); + overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); + overloadedSetter.append(" * Accepts a Long value and converts it to Integer with bounds checking.\n"); + overloadedSetter.append(" * @param value The Long value to set\n"); + overloadedSetter.append(" * @throws org.apache.avro.AvroRuntimeException if the value is outside the Integer range\n"); + overloadedSetter.append(" */\n"); + overloadedSetter.append(" public void ").append(methodName).append("(java.lang.Long value) {\n"); + overloadedSetter.append(" if (value == null) {\n"); + overloadedSetter.append(" this.").append(fieldName).append(" = null;\n"); + overloadedSetter.append(" } else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); + overloadedSetter.append(" this.").append(fieldName).append(" = value.intValue();\n"); + overloadedSetter.append(" } else {\n"); + overloadedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to Integer\");\n"); + overloadedSetter.append(" }\n"); + overloadedSetter.append(" }"); + } else if (JAVA_LANG_LONG.equals(fieldType)) { // For Long fields, add an overloaded setter that accepts Integer - enhancedSetter.append(" public void ").append(methodName).append("(java.lang.Long value) {\n"); - enhancedSetter.append(" this.").append(fieldName).append(" = value;\n"); - enhancedSetter.append(" }\n\n"); - - enhancedSetter.append(" public void ").append(methodName).append("(java.lang.Integer value) {\n"); - enhancedSetter.append(" if (value == null) {\n"); - enhancedSetter.append(" this.").append(fieldName).append(" = null;\n"); - enhancedSetter.append(" } else {\n"); - enhancedSetter.append(" this.").append(fieldName).append(" = value.longValue();\n"); - enhancedSetter.append(" }\n"); - enhancedSetter.append(" }\n\n"); + overloadedSetter.append("\n\n /**\n"); + overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); + overloadedSetter.append(" * Accepts an Integer value and converts it to Long.\n"); + overloadedSetter.append(" * @param value The Integer value to set\n"); + overloadedSetter.append(" */\n"); + overloadedSetter.append(" public void ").append(methodName).append("(java.lang.Integer value) {\n"); + overloadedSetter.append(" if (value == null) {\n"); + overloadedSetter.append(" this.").append(fieldName).append(" = null;\n"); + overloadedSetter.append(" } else {\n"); + overloadedSetter.append(" this.").append(fieldName).append(" = value.longValue();\n"); + overloadedSetter.append(" }\n"); + overloadedSetter.append(" }"); } - // Replace the original setter with the enhanced version - result.replace(setterMatcher.start(), setterMatcher.end(), enhancedSetter.toString()); + // Insert the overloaded setter after the existing setter + if (overloadedSetter.length() > 0) { + result.insert(setterEnd, overloadedSetter); + insertOffset = setterEnd + overloadedSetter.length(); + } } } From 0b2af159255f2cc0f78eaf4ab5050fb982a820ec Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Thu, 5 Jun 2025 11:32:01 -0700 Subject: [PATCH 09/15] Adding builder test records, fixed overloaded setters for primitives --- .../compatibility/CodeTransformations.java | 200 ++++++++++-------- .../IntsAndLongsWithBuilder.avsc | 25 +++ .../IntsAndLongsWithBuilder.avsc | 25 +++ .../IntsAndLongsWithBuilder.avsc | 25 +++ .../IntsAndLongsWithBuilder.avsc | 25 +++ 5 files changed, 208 insertions(+), 92 deletions(-) create mode 100644 helper/tests/codegen-110/src/main/compat-avro-w-builders/under110wbuilders/IntsAndLongsWithBuilder.avsc create mode 100644 helper/tests/codegen-111/src/main/compat-avro-w-builders/under111wbuilders/IntsAndLongsWithBuilder.avsc create mode 100644 helper/tests/codegen-18/src/main/compat-avro-w-builders/under18wbuilders/IntsAndLongsWithBuilder.avsc create mode 100644 helper/tests/codegen-19/src/main/compat-avro-w-builders/under19wbuilders/IntsAndLongsWithBuilder.avsc diff --git a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java index c7065a91a..1af751d37 100644 --- a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java +++ b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java @@ -1076,23 +1076,23 @@ public static String addOverloadedNumericSetterMethods(String code) { // First, identify all numeric fields in the class (both primitive and boxed) Map fieldTypes = new HashMap<>(); // fieldName -> type - // Pattern to match primitive field declarations - Pattern primitiveFieldPattern = Pattern.compile("private\\s+(int|long)\\s+(\\w+)\\s*;"); + // Pattern to match primitive field declarations - both public and private + Pattern primitiveFieldPattern = Pattern.compile("(public|private)\\s+(int|long)\\s+(\\w+)\\s*;"); Matcher primitiveFieldMatcher = primitiveFieldPattern.matcher(code); while (primitiveFieldMatcher.find()) { - String fieldType = primitiveFieldMatcher.group(1); - String fieldName = primitiveFieldMatcher.group(2); + String fieldType = primitiveFieldMatcher.group(2); + String fieldName = primitiveFieldMatcher.group(3); fieldTypes.put(fieldName, fieldType); } // Pattern to match boxed field declarations - Pattern boxedFieldPattern = Pattern.compile("private\\s+(java\\.lang\\.Integer|java\\.lang\\.Long)\\s+(\\w+)\\s*;"); + Pattern boxedFieldPattern = Pattern.compile("(public|private)\\s+(java\\.lang\\.Integer|java\\.lang\\.Long)\\s+(\\w+)\\s*;"); Matcher boxedFieldMatcher = boxedFieldPattern.matcher(code); while (boxedFieldMatcher.find()) { - String fieldType = boxedFieldMatcher.group(1); - String fieldName = boxedFieldMatcher.group(2); + String fieldType = boxedFieldMatcher.group(2); + String fieldName = boxedFieldMatcher.group(3); fieldTypes.put(fieldName, fieldType); } @@ -1101,8 +1101,7 @@ public static String addOverloadedNumericSetterMethods(String code) { } StringBuilder result = new StringBuilder(code); - int insertOffset = 0; // Track the offset as we insert new methods - + // For each field, find its setter method and add an overloaded version for (Map.Entry entry : fieldTypes.entrySet()) { String fieldName = entry.getKey(); @@ -1110,92 +1109,109 @@ public static String addOverloadedNumericSetterMethods(String code) { // Convert field name to method name (camelCase to PascalCase) String methodName = "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); - - // Pattern to match the setter method - handle both primitive and boxed types - Pattern setterPattern; - if ("int".equals(fieldType) || "long".equals(fieldType)) { - setterPattern = Pattern.compile( - "public\\s+void\\s+" + methodName + "\\s*\\(\\s*" + fieldType + "\\s+\\w+\\s*\\)\\s*\\{[^}]+\\}" - ); - } else { - setterPattern = Pattern.compile( - "public\\s+void\\s+" + methodName + "\\s*\\(\\s*" + fieldType + "\\s+value\\s*\\)\\s*\\{\\s*" + - "this\\." + fieldName + "\\s*=\\s*value;\\s*" + - "\\}" - ); + + // Find the position of the existing setter method + String setterSignature = "public void " + methodName + "(" + fieldType + " "; + int setterStart = code.indexOf(setterSignature); + + if (setterStart == -1) { + continue; // Setter not found + } + + // Find the end of the setter method (closing brace) + int braceCount = 1; + int setterEnd = code.indexOf("{", setterStart); + if (setterEnd == -1) continue; + + setterEnd++; + while (setterEnd < code.length() && braceCount > 0) { + char c = code.charAt(setterEnd); + if (c == '{') braceCount++; + else if (c == '}') braceCount--; + setterEnd++; + } + + if (braceCount != 0) continue; // Couldn't find proper end of method + + StringBuilder overloadedSetter = new StringBuilder(); + + // Add the appropriate overloaded setter based on field type + if ("int".equals(fieldType)) { + // For int fields, add an overloaded setter that accepts long with bounds checking + overloadedSetter.append("\n\n /**\n"); + overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); + overloadedSetter.append(" * Accepts a long value and converts it to int with bounds checking.\n"); + overloadedSetter.append(" * @param value The long value to set\n"); + overloadedSetter.append(" * @throws org.apache.avro.AvroRuntimeException if the value is outside the int range\n"); + overloadedSetter.append(" */\n"); + overloadedSetter.append(" public void ").append(methodName).append("(long value) {\n"); + overloadedSetter.append(" if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); + overloadedSetter.append(" this.").append(fieldName).append(" = (int) value;\n"); + overloadedSetter.append(" } else {\n"); + overloadedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to int\");\n"); + overloadedSetter.append(" }\n"); + overloadedSetter.append(" }"); + } else if ("long".equals(fieldType)) { + // For long fields, add an overloaded setter that accepts int + overloadedSetter.append("\n\n /**\n"); + overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); + overloadedSetter.append(" * Accepts an int value and converts it to long.\n"); + overloadedSetter.append(" * @param value The int value to set\n"); + overloadedSetter.append(" */\n"); + overloadedSetter.append(" public void ").append(methodName).append("(int value) {\n"); + overloadedSetter.append(" this.").append(fieldName).append(" = value;\n"); + overloadedSetter.append(" }"); + } else if (JAVA_LANG_INTEGER.equals(fieldType)) { + // For Integer fields, add an overloaded setter that accepts Long with bounds checking + overloadedSetter.append("\n\n /**\n"); + overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); + overloadedSetter.append(" * Accepts a Long value and converts it to Integer with bounds checking.\n"); + overloadedSetter.append(" * @param value The Long value to set\n"); + overloadedSetter.append(" * @throws org.apache.avro.AvroRuntimeException if the value is outside the Integer range\n"); + overloadedSetter.append(" */\n"); + overloadedSetter.append(" public void ").append(methodName).append("(java.lang.Long value) {\n"); + overloadedSetter.append(" if (value == null) {\n"); + overloadedSetter.append(" this.").append(fieldName).append(" = null;\n"); + overloadedSetter.append(" } else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); + overloadedSetter.append(" this.").append(fieldName).append(" = value.intValue();\n"); + overloadedSetter.append(" } else {\n"); + overloadedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to Integer\");\n"); + overloadedSetter.append(" }\n"); + overloadedSetter.append(" }"); + } else if (JAVA_LANG_LONG.equals(fieldType)) { + // For Long fields, add an overloaded setter that accepts Integer + overloadedSetter.append("\n\n /**\n"); + overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); + overloadedSetter.append(" * Accepts an Integer value and converts it to Long.\n"); + overloadedSetter.append(" * @param value The Integer value to set\n"); + overloadedSetter.append(" */\n"); + overloadedSetter.append(" public void ").append(methodName).append("(java.lang.Integer value) {\n"); + overloadedSetter.append(" if (value == null) {\n"); + overloadedSetter.append(" this.").append(fieldName).append(" = null;\n"); + overloadedSetter.append(" } else {\n"); + overloadedSetter.append(" this.").append(fieldName).append(" = value.longValue();\n"); + overloadedSetter.append(" }\n"); + overloadedSetter.append(" }"); } - Matcher setterMatcher = setterPattern.matcher(result); - - // Find the existing setter method - if (setterMatcher.find(insertOffset)) { - int setterEnd = setterMatcher.end(); - StringBuilder overloadedSetter = new StringBuilder(); - - // Add the appropriate overloaded setter based on field type - if ("int".equals(fieldType)) { - // For int fields, add an overloaded setter that accepts long with bounds checking - overloadedSetter.append("\n\n /**\n"); - overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); - overloadedSetter.append(" * Accepts a long value and converts it to int with bounds checking.\n"); - overloadedSetter.append(" * @param value The long value to set\n"); - overloadedSetter.append(" * @throws org.apache.avro.AvroRuntimeException if the value is outside the int range\n"); - overloadedSetter.append(" */\n"); - overloadedSetter.append(" public void ").append(methodName).append("(long value) {\n"); - overloadedSetter.append(" if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); - overloadedSetter.append(" this.").append(fieldName).append(" = (int) value;\n"); - overloadedSetter.append(" } else {\n"); - overloadedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to int\");\n"); - overloadedSetter.append(" }\n"); - overloadedSetter.append(" }"); - } else if ("long".equals(fieldType)) { - // For long fields, add an overloaded setter that accepts int - overloadedSetter.append("\n\n /**\n"); - overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); - overloadedSetter.append(" * Accepts an int value and converts it to long.\n"); - overloadedSetter.append(" * @param value The int value to set\n"); - overloadedSetter.append(" */\n"); - overloadedSetter.append(" public void ").append(methodName).append("(int value) {\n"); - overloadedSetter.append(" this.").append(fieldName).append(" = value;\n"); - overloadedSetter.append(" }"); - } else if (JAVA_LANG_INTEGER.equals(fieldType)) { - // For Integer fields, add an overloaded setter that accepts Long with bounds checking - overloadedSetter.append("\n\n /**\n"); - overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); - overloadedSetter.append(" * Accepts a Long value and converts it to Integer with bounds checking.\n"); - overloadedSetter.append(" * @param value The Long value to set\n"); - overloadedSetter.append(" * @throws org.apache.avro.AvroRuntimeException if the value is outside the Integer range\n"); - overloadedSetter.append(" */\n"); - overloadedSetter.append(" public void ").append(methodName).append("(java.lang.Long value) {\n"); - overloadedSetter.append(" if (value == null) {\n"); - overloadedSetter.append(" this.").append(fieldName).append(" = null;\n"); - overloadedSetter.append(" } else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); - overloadedSetter.append(" this.").append(fieldName).append(" = value.intValue();\n"); - overloadedSetter.append(" } else {\n"); - overloadedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to Integer\");\n"); - overloadedSetter.append(" }\n"); - overloadedSetter.append(" }"); - } else if (JAVA_LANG_LONG.equals(fieldType)) { - // For Long fields, add an overloaded setter that accepts Integer - overloadedSetter.append("\n\n /**\n"); - overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); - overloadedSetter.append(" * Accepts an Integer value and converts it to Long.\n"); - overloadedSetter.append(" * @param value The Integer value to set\n"); - overloadedSetter.append(" */\n"); - overloadedSetter.append(" public void ").append(methodName).append("(java.lang.Integer value) {\n"); - overloadedSetter.append(" if (value == null) {\n"); - overloadedSetter.append(" this.").append(fieldName).append(" = null;\n"); - overloadedSetter.append(" } else {\n"); - overloadedSetter.append(" this.").append(fieldName).append(" = value.longValue();\n"); - overloadedSetter.append(" }\n"); - overloadedSetter.append(" }"); - } - + // Check if the overloaded method already exists in the code + String overloadSignature = null; + if ("int".equals(fieldType)) { + overloadSignature = "public void " + methodName + "(long "; + } else if ("long".equals(fieldType)) { + overloadSignature = "public void " + methodName + "(int "; + } else if (JAVA_LANG_INTEGER.equals(fieldType)) { + overloadSignature = "public void " + methodName + "(java.lang.Long "; + } else if (JAVA_LANG_LONG.equals(fieldType)) { + overloadSignature = "public void " + methodName + "(java.lang.Integer "; + } + + // Only add if it doesn't already exist + if (overloadSignature != null && !code.contains(overloadSignature)) { // Insert the overloaded setter after the existing setter - if (overloadedSetter.length() > 0) { - result.insert(setterEnd, overloadedSetter); - insertOffset = setterEnd + overloadedSetter.length(); - } + result.insert(setterEnd, overloadedSetter); + // Update the code string for subsequent searches + code = result.toString(); } } diff --git a/helper/tests/codegen-110/src/main/compat-avro-w-builders/under110wbuilders/IntsAndLongsWithBuilder.avsc b/helper/tests/codegen-110/src/main/compat-avro-w-builders/under110wbuilders/IntsAndLongsWithBuilder.avsc new file mode 100644 index 000000000..fa0395191 --- /dev/null +++ b/helper/tests/codegen-110/src/main/compat-avro-w-builders/under110wbuilders/IntsAndLongsWithBuilder.avsc @@ -0,0 +1,25 @@ +{ + "type": "record", + "namespace": "under110wbuilders", + "name": "IntsAndLongsWithBuilder", + "fields": [ + { + "name": "longField", + "type": "long" + }, + { + "name": "intField", + "type": "int" + }, + { + "name": "boxedLongField", + "type": ["null", "long"], + "default": null + }, + { + "name": "boxedIntField", + "type": ["null", "int"], + "default": null + } + ] +} diff --git a/helper/tests/codegen-111/src/main/compat-avro-w-builders/under111wbuilders/IntsAndLongsWithBuilder.avsc b/helper/tests/codegen-111/src/main/compat-avro-w-builders/under111wbuilders/IntsAndLongsWithBuilder.avsc new file mode 100644 index 000000000..29a7c3c5c --- /dev/null +++ b/helper/tests/codegen-111/src/main/compat-avro-w-builders/under111wbuilders/IntsAndLongsWithBuilder.avsc @@ -0,0 +1,25 @@ +{ + "type": "record", + "namespace": "under111wbuilders", + "name": "IntsAndLongsWithBuilder", + "fields": [ + { + "name": "longField", + "type": "long" + }, + { + "name": "intField", + "type": "int" + }, + { + "name": "boxedLongField", + "type": ["null", "long"], + "default": null + }, + { + "name": "boxedIntField", + "type": ["null", "int"], + "default": null + } + ] +} diff --git a/helper/tests/codegen-18/src/main/compat-avro-w-builders/under18wbuilders/IntsAndLongsWithBuilder.avsc b/helper/tests/codegen-18/src/main/compat-avro-w-builders/under18wbuilders/IntsAndLongsWithBuilder.avsc new file mode 100644 index 000000000..95e4b4738 --- /dev/null +++ b/helper/tests/codegen-18/src/main/compat-avro-w-builders/under18wbuilders/IntsAndLongsWithBuilder.avsc @@ -0,0 +1,25 @@ +{ + "type": "record", + "namespace": "under18wbuilders", + "name": "IntsAndLongsWithBuilder", + "fields": [ + { + "name": "longField", + "type": "long" + }, + { + "name": "intField", + "type": "int" + }, + { + "name": "boxedLongField", + "type": ["null", "long"], + "default": null + }, + { + "name": "boxedIntField", + "type": ["null", "int"], + "default": null + } + ] +} diff --git a/helper/tests/codegen-19/src/main/compat-avro-w-builders/under19wbuilders/IntsAndLongsWithBuilder.avsc b/helper/tests/codegen-19/src/main/compat-avro-w-builders/under19wbuilders/IntsAndLongsWithBuilder.avsc new file mode 100644 index 000000000..09fb0fbec --- /dev/null +++ b/helper/tests/codegen-19/src/main/compat-avro-w-builders/under19wbuilders/IntsAndLongsWithBuilder.avsc @@ -0,0 +1,25 @@ +{ + "type": "record", + "namespace": "under19wbuilders", + "name": "IntsAndLongsWithBuilder", + "fields": [ + { + "name": "longField", + "type": "long" + }, + { + "name": "intField", + "type": "int" + }, + { + "name": "boxedLongField", + "type": ["null", "long"], + "default": null + }, + { + "name": "boxedIntField", + "type": ["null", "int"], + "default": null + } + ] +} From 4232c758b00d697c8cdfca7d8dea492fd550d6b5 Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Mon, 9 Jun 2025 16:38:11 -0700 Subject: [PATCH 10/15] Unit test --- .../CodeTransformationsAvro110Test.java | 80 +++++++++++++++++++ .../avro14/CodeTransformationsAvro14Test.java | 80 +++++++++++++++++++ .../avro15/CodeTransformationsAvro15Test.java | 80 +++++++++++++++++++ .../avro16/CodeTransformationsAvro16Test.java | 80 +++++++++++++++++++ .../avro17/CodeTransformationsAvro17Test.java | 80 +++++++++++++++++++ .../avro18/CodeTransformationsAvro18Test.java | 80 +++++++++++++++++++ .../avro19/CodeTransformationsAvro19Test.java | 80 +++++++++++++++++++ .../src/main/resources/IntsAndLongs.avsc | 25 ++++++ 8 files changed, 585 insertions(+) create mode 100644 helper/tests/helper-tests-common/src/main/resources/IntsAndLongs.avsc diff --git a/helper/tests/helper-tests-110/src/test/java/com/linkedin/avroutil1/compatibility/avro110/CodeTransformationsAvro110Test.java b/helper/tests/helper-tests-110/src/test/java/com/linkedin/avroutil1/compatibility/avro110/CodeTransformationsAvro110Test.java index 24268670d..53cc6b1bb 100644 --- a/helper/tests/helper-tests-110/src/test/java/com/linkedin/avroutil1/compatibility/avro110/CodeTransformationsAvro110Test.java +++ b/helper/tests/helper-tests-110/src/test/java/com/linkedin/avroutil1/compatibility/avro110/CodeTransformationsAvro110Test.java @@ -158,6 +158,86 @@ public void testBuilders() throws Exception { Assert.assertNotNull(NormalRecordWithoutReferences.newBuilder()); } + @Test + public void testEnhanceNumericPutMethod() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.enhanceNumericPutMethod(originalCode); + + // Verify the enhanced code contains the type checking for numeric conversions + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Long)")); + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Integer)")); + + // Compile the enhanced code to verify it's valid Java + try { + CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced put method code should compile without errors"); + } + } + + @Test + public void testAddOverloadedNumericSetterMethods() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericSetterMethods(originalCode); + + // Verify the enhanced code contains the overloaded setters + Assert.assertTrue(enhancedCode.contains("public void setIntField(long value)")); + Assert.assertTrue(enhancedCode.contains("public void setLongField(int value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedIntField(java.lang.Long value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced setter methods code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getMethod("setIntField", long.class)); + Assert.assertNotNull(generatedClass.getMethod("setLongField", int.class)); + Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); + Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); + } + + @Test + public void testAddOverloadedNumericConstructor() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericConstructor(originalCode); + + // Verify the enhanced code contains the overloaded constructor with swapped types + Assert.assertTrue(enhancedCode.contains("public " + schema.getName() + "(java.lang.Integer longField, java.lang.Long " + + "intField, java.lang.Integer boxedLongField, java.lang.Long boxedIntField)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced constructor code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class)); + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class)); + } + private String runNativeCodegen(Schema schema) throws Exception { File outputRoot = Files.createTempDirectory(null).toFile(); SpecificCompiler compiler = new SpecificCompiler(schema); diff --git a/helper/tests/helper-tests-14/src/test/java/com/linkedin/avroutil1/compatibility/avro14/CodeTransformationsAvro14Test.java b/helper/tests/helper-tests-14/src/test/java/com/linkedin/avroutil1/compatibility/avro14/CodeTransformationsAvro14Test.java index f0c6fd3c2..f9b733dda 100644 --- a/helper/tests/helper-tests-14/src/test/java/com/linkedin/avroutil1/compatibility/avro14/CodeTransformationsAvro14Test.java +++ b/helper/tests/helper-tests-14/src/test/java/com/linkedin/avroutil1/compatibility/avro14/CodeTransformationsAvro14Test.java @@ -148,6 +148,86 @@ public void testAlternateAvscUnderAvro14WithEscaping() throws Exception { Assert.assertEquals(inCode, schema); //no (significant) harm done } + @Test + public void testEnhanceNumericPutMethod() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.enhanceNumericPutMethod(originalCode); + + // Verify the enhanced code contains the type checking for numeric conversions + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Long)")); + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Integer)")); + + // Compile the enhanced code to verify it's valid Java + try { + CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced put method code should compile without errors"); + } + } + + @Test + public void testAddOverloadedNumericSetterMethods() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericSetterMethods(originalCode); + + // Verify the enhanced code contains the overloaded setters + Assert.assertTrue(enhancedCode.contains("public void setIntField(long value)")); + Assert.assertTrue(enhancedCode.contains("public void setLongField(int value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedIntField(java.lang.Long value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced setter methods code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getMethod("setIntField", long.class)); + Assert.assertNotNull(generatedClass.getMethod("setLongField", int.class)); + Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); + Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); + } + + @Test + public void testAddOverloadedNumericConstructor() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericConstructor(originalCode); + + // Verify the enhanced code contains the overloaded constructor with swapped types + Assert.assertTrue(enhancedCode.contains("public " + schema.getName() + "(java.lang.Integer longField, java.lang.Long " + + "intField, java.lang.Integer boxedLongField, java.lang.Long boxedIntField)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced constructor code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class)); + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class)); + } + private String runNativeCodegen(Schema schema) throws Exception { File outputRoot = Files.createTempDirectory(null).toFile(); return runNativeCodegen(schema, outputRoot); diff --git a/helper/tests/helper-tests-15/src/test/java/com/linkedin/avroutil1/compatibility/avro15/CodeTransformationsAvro15Test.java b/helper/tests/helper-tests-15/src/test/java/com/linkedin/avroutil1/compatibility/avro15/CodeTransformationsAvro15Test.java index 5f97a0e28..748ecf7f5 100644 --- a/helper/tests/helper-tests-15/src/test/java/com/linkedin/avroutil1/compatibility/avro15/CodeTransformationsAvro15Test.java +++ b/helper/tests/helper-tests-15/src/test/java/com/linkedin/avroutil1/compatibility/avro15/CodeTransformationsAvro15Test.java @@ -64,6 +64,86 @@ public void testTransformAvro15RecordWithMultilineDoc() throws Exception { Assert.assertNotNull(transformedClass); } + @Test + public void testEnhanceNumericPutMethod() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.enhanceNumericPutMethod(originalCode); + + // Verify the enhanced code contains the type checking for numeric conversions + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Long)")); + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Integer)")); + + // Compile the enhanced code to verify it's valid Java + try { + CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced put method code should compile without errors"); + } + } + + @Test + public void testAddOverloadedNumericSetterMethods() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericSetterMethods(originalCode); + + // Verify the enhanced code contains the overloaded setters + Assert.assertTrue(enhancedCode.contains("public void setIntField(long value)")); + Assert.assertTrue(enhancedCode.contains("public void setLongField(int value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedIntField(java.lang.Long value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced setter methods code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getMethod("setIntField", long.class)); + Assert.assertNotNull(generatedClass.getMethod("setLongField", int.class)); + Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); + Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); + } + + @Test + public void testAddOverloadedNumericConstructor() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericConstructor(originalCode); + + // Verify the enhanced code contains the overloaded constructor with swapped types + Assert.assertTrue(enhancedCode.contains("public " + schema.getName() + "(java.lang.Integer longField, java.lang.Long " + + "intField, java.lang.Integer boxedLongField, java.lang.Long boxedIntField)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced constructor code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class)); + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class)); + } + private String runNativeCodegen(Schema schema) throws Exception { File outputRoot = Files.createTempDirectory(null).toFile(); SpecificCompiler compiler = new SpecificCompiler(schema); diff --git a/helper/tests/helper-tests-16/src/test/java/com/linkedin/avroutil1/compatibility/avro16/CodeTransformationsAvro16Test.java b/helper/tests/helper-tests-16/src/test/java/com/linkedin/avroutil1/compatibility/avro16/CodeTransformationsAvro16Test.java index 52251b002..484651bc1 100644 --- a/helper/tests/helper-tests-16/src/test/java/com/linkedin/avroutil1/compatibility/avro16/CodeTransformationsAvro16Test.java +++ b/helper/tests/helper-tests-16/src/test/java/com/linkedin/avroutil1/compatibility/avro16/CodeTransformationsAvro16Test.java @@ -51,6 +51,86 @@ public void testTransformAvro16HugeRecord() throws Exception { Assert.assertNotNull(transformedClass); } + @Test + public void testEnhanceNumericPutMethod() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.enhanceNumericPutMethod(originalCode); + + // Verify the enhanced code contains the type checking for numeric conversions + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Long)")); + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Integer)")); + + // Compile the enhanced code to verify it's valid Java + try { + CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced put method code should compile without errors"); + } + } + + @Test + public void testAddOverloadedNumericSetterMethods() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericSetterMethods(originalCode); + + // Verify the enhanced code contains the overloaded setters + Assert.assertTrue(enhancedCode.contains("public void setIntField(long value)")); + Assert.assertTrue(enhancedCode.contains("public void setLongField(int value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedIntField(java.lang.Long value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced setter methods code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getMethod("setIntField", long.class)); + Assert.assertNotNull(generatedClass.getMethod("setLongField", int.class)); + Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); + Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); + } + + @Test + public void testAddOverloadedNumericConstructor() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericConstructor(originalCode); + + // Verify the enhanced code contains the overloaded constructor with swapped types + Assert.assertTrue(enhancedCode.contains("public " + schema.getName() + "(java.lang.Integer longField, java.lang.Long " + + "intField, java.lang.Integer boxedLongField, java.lang.Long boxedIntField)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced constructor code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class)); + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class)); + } + private String runNativeCodegen(Schema schema) throws Exception { File outputRoot = Files.createTempDirectory(null).toFile(); SpecificCompiler compiler = new SpecificCompiler(schema); diff --git a/helper/tests/helper-tests-17/src/test/java/com/linkedin/avroutil1/compatibility/avro17/CodeTransformationsAvro17Test.java b/helper/tests/helper-tests-17/src/test/java/com/linkedin/avroutil1/compatibility/avro17/CodeTransformationsAvro17Test.java index f44dc5cf5..eb305e28c 100644 --- a/helper/tests/helper-tests-17/src/test/java/com/linkedin/avroutil1/compatibility/avro17/CodeTransformationsAvro17Test.java +++ b/helper/tests/helper-tests-17/src/test/java/com/linkedin/avroutil1/compatibility/avro17/CodeTransformationsAvro17Test.java @@ -51,6 +51,86 @@ public void testTransformAvro17HugeRecord() throws Exception { Assert.assertNotNull(transformedClass); } + @Test + public void testEnhanceNumericPutMethod() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.enhanceNumericPutMethod(originalCode); + + // Verify the enhanced code contains the type checking for numeric conversions + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Long)")); + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Integer)")); + + // Compile the enhanced code to verify it's valid Java + try { + CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced put method code should compile without errors"); + } + } + + @Test + public void testAddOverloadedNumericSetterMethods() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericSetterMethods(originalCode); + + // Verify the enhanced code contains the overloaded setters + Assert.assertTrue(enhancedCode.contains("public void setIntField(long value)")); + Assert.assertTrue(enhancedCode.contains("public void setLongField(int value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedIntField(java.lang.Long value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced setter methods code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getMethod("setIntField", long.class)); + Assert.assertNotNull(generatedClass.getMethod("setLongField", int.class)); + Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); + Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); + } + + @Test + public void testAddOverloadedNumericConstructor() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericConstructor(originalCode); + + // Verify the enhanced code contains the overloaded constructor with swapped types + Assert.assertTrue(enhancedCode.contains("public " + schema.getName() + "(java.lang.Integer longField, java.lang.Long " + + "intField, java.lang.Integer boxedLongField, java.lang.Long boxedIntField)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced constructor code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class)); + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class)); + } + private String runNativeCodegen(Schema schema) throws Exception { File outputRoot = Files.createTempDirectory(null).toFile(); SpecificCompiler compiler = new SpecificCompiler(schema); diff --git a/helper/tests/helper-tests-18/src/test/java/com/linkedin/avroutil1/compatibility/avro18/CodeTransformationsAvro18Test.java b/helper/tests/helper-tests-18/src/test/java/com/linkedin/avroutil1/compatibility/avro18/CodeTransformationsAvro18Test.java index 006a8cffd..54fd61fe1 100644 --- a/helper/tests/helper-tests-18/src/test/java/com/linkedin/avroutil1/compatibility/avro18/CodeTransformationsAvro18Test.java +++ b/helper/tests/helper-tests-18/src/test/java/com/linkedin/avroutil1/compatibility/avro18/CodeTransformationsAvro18Test.java @@ -58,6 +58,86 @@ public void testBuilders() throws Exception { Assert.assertNotNull(NormalRecordWithoutReferences.newBuilder()); } + @Test + public void testEnhanceNumericPutMethod() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.enhanceNumericPutMethod(originalCode); + + // Verify the enhanced code contains the type checking for numeric conversions + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Long)")); + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Integer)")); + + // Compile the enhanced code to verify it's valid Java + try { + CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced put method code should compile without errors"); + } + } + + @Test + public void testAddOverloadedNumericSetterMethods() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericSetterMethods(originalCode); + + // Verify the enhanced code contains the overloaded setters + Assert.assertTrue(enhancedCode.contains("public void setIntField(long value)")); + Assert.assertTrue(enhancedCode.contains("public void setLongField(int value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedIntField(java.lang.Long value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced setter methods code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getMethod("setIntField", long.class)); + Assert.assertNotNull(generatedClass.getMethod("setLongField", int.class)); + Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); + Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); + } + + @Test + public void testAddOverloadedNumericConstructor() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericConstructor(originalCode); + + // Verify the enhanced code contains the overloaded constructor with swapped types + Assert.assertTrue(enhancedCode.contains("public " + schema.getName() + "(java.lang.Integer longField, java.lang.Long " + + "intField, java.lang.Integer boxedLongField, java.lang.Long boxedIntField)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced constructor code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class)); + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class)); + } + private String runNativeCodegen(Schema schema) throws Exception { File outputRoot = Files.createTempDirectory(null).toFile(); SpecificCompiler compiler = new SpecificCompiler(schema); diff --git a/helper/tests/helper-tests-19/src/test/java/com/linkedin/avroutil1/compatibility/avro19/CodeTransformationsAvro19Test.java b/helper/tests/helper-tests-19/src/test/java/com/linkedin/avroutil1/compatibility/avro19/CodeTransformationsAvro19Test.java index 79f98b38a..09e55dfab 100644 --- a/helper/tests/helper-tests-19/src/test/java/com/linkedin/avroutil1/compatibility/avro19/CodeTransformationsAvro19Test.java +++ b/helper/tests/helper-tests-19/src/test/java/com/linkedin/avroutil1/compatibility/avro19/CodeTransformationsAvro19Test.java @@ -146,6 +146,86 @@ public void testBuilders() throws Exception { Assert.assertNotNull(NormalRecordWithoutReferences.newBuilder()); } + @Test + public void testEnhanceNumericPutMethod() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.enhanceNumericPutMethod(originalCode); + + // Verify the enhanced code contains the type checking for numeric conversions + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Long)")); + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Integer)")); + + // Compile the enhanced code to verify it's valid Java + try { + CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced put method code should compile without errors"); + } + } + + @Test + public void testAddOverloadedNumericSetterMethods() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericSetterMethods(originalCode); + + // Verify the enhanced code contains the overloaded setters + Assert.assertTrue(enhancedCode.contains("public void setIntField(long value)")); + Assert.assertTrue(enhancedCode.contains("public void setLongField(int value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedIntField(java.lang.Long value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced setter methods code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getMethod("setIntField", long.class)); + Assert.assertNotNull(generatedClass.getMethod("setLongField", int.class)); + Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); + Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); + } + + @Test + public void testAddOverloadedNumericConstructor() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericConstructor(originalCode); + + // Verify the enhanced code contains the overloaded constructor with swapped types + Assert.assertTrue(enhancedCode.contains("public " + schema.getName() + "(java.lang.Integer longField, java.lang.Long " + + "intField, java.lang.Integer boxedLongField, java.lang.Long boxedIntField)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced constructor code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class)); + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class)); + } + private String runNativeCodegen(Schema schema) throws Exception { File outputRoot = Files.createTempDirectory(null).toFile(); SpecificCompiler compiler = new SpecificCompiler(schema); diff --git a/helper/tests/helper-tests-common/src/main/resources/IntsAndLongs.avsc b/helper/tests/helper-tests-common/src/main/resources/IntsAndLongs.avsc new file mode 100644 index 000000000..e80229e9f --- /dev/null +++ b/helper/tests/helper-tests-common/src/main/resources/IntsAndLongs.avsc @@ -0,0 +1,25 @@ +{ + "type": "record", + "namespace": "com.acme", + "name": "IntsAndLongs", + "fields": [ + { + "name": "longField", + "type": "long" + }, + { + "name": "intField", + "type": "int" + }, + { + "name": "boxedLongField", + "type": ["null", "long"], + "default": null + }, + { + "name": "boxedIntField", + "type": ["null", "int"], + "default": null + } + ] +} From 40acf6291b20ebcad5e9b5ccb0b1f92d0fe4192c Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Wed, 11 Jun 2025 17:38:52 -0700 Subject: [PATCH 11/15] Builders --- .../compatibility/CodeTransformations.java | 201 +++++++++++++++++- 1 file changed, 192 insertions(+), 9 deletions(-) diff --git a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java index 1af751d37..a4d178b82 100644 --- a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java +++ b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java @@ -154,6 +154,7 @@ public static String applyAll( fixed = CodeTransformations.removeReferencesToNewClassesFromBuilders(fixed, generatedBy, minSupportedVersion, maxSupportedVersion); } + fixed = CodeTransformations.addOverloadedNumericBuilderSetterMethods(fixed); //1.7+ features //this is considered harmless enough we can keep doing it? fixed = CodeTransformations.removeAvroGeneratedAnnotation(fixed, minSupportedVersion, maxSupportedVersion); @@ -1101,7 +1102,7 @@ public static String addOverloadedNumericSetterMethods(String code) { } StringBuilder result = new StringBuilder(code); - + // For each field, find its setter method and add an overloaded version for (Map.Entry entry : fieldTypes.entrySet()) { String fieldName = entry.getKey(); @@ -1109,20 +1110,20 @@ public static String addOverloadedNumericSetterMethods(String code) { // Convert field name to method name (camelCase to PascalCase) String methodName = "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); - + // Find the position of the existing setter method String setterSignature = "public void " + methodName + "(" + fieldType + " "; int setterStart = code.indexOf(setterSignature); - + if (setterStart == -1) { continue; // Setter not found } - + // Find the end of the setter method (closing brace) int braceCount = 1; int setterEnd = code.indexOf("{", setterStart); if (setterEnd == -1) continue; - + setterEnd++; while (setterEnd < code.length() && braceCount > 0) { char c = code.charAt(setterEnd); @@ -1130,9 +1131,9 @@ public static String addOverloadedNumericSetterMethods(String code) { else if (c == '}') braceCount--; setterEnd++; } - + if (braceCount != 0) continue; // Couldn't find proper end of method - + StringBuilder overloadedSetter = new StringBuilder(); // Add the appropriate overloaded setter based on field type @@ -1159,7 +1160,7 @@ public static String addOverloadedNumericSetterMethods(String code) { overloadedSetter.append(" * @param value The int value to set\n"); overloadedSetter.append(" */\n"); overloadedSetter.append(" public void ").append(methodName).append("(int value) {\n"); - overloadedSetter.append(" this.").append(fieldName).append(" = value;\n"); + overloadedSetter.append(" this.").append(fieldName).append(" = (long) value;\n"); overloadedSetter.append(" }"); } else if (JAVA_LANG_INTEGER.equals(fieldType)) { // For Integer fields, add an overloaded setter that accepts Long with bounds checking @@ -1205,7 +1206,7 @@ public static String addOverloadedNumericSetterMethods(String code) { } else if (JAVA_LANG_LONG.equals(fieldType)) { overloadSignature = "public void " + methodName + "(java.lang.Integer "; } - + // Only add if it doesn't already exist if (overloadSignature != null && !code.contains(overloadSignature)) { // Insert the overloaded setter after the existing setter @@ -1482,6 +1483,188 @@ private static void processParameter(String param, } } + + /** + * Adds overloaded setter methods for numeric fields in the builder class to allow setting Integer fields with Long values + * and Long fields with Integer values. This improves type compatibility when working with numeric fields in the builder pattern. + * This method preserves existing builder setter methods and only adds the overloaded versions. + * + * @param code generated code + * @return transformed code with overloaded numeric builder setter methods + */ + public static String addOverloadedNumericBuilderSetterMethods(String code) { + if (code == null || code.isEmpty()) { + return code; + } + + // First, identify the builder class in the code + Pattern builderClassPattern = Pattern.compile("public static class Builder[^{]*\\{"); + Matcher builderClassMatcher = builderClassPattern.matcher(code); + + if (!builderClassMatcher.find()) { + return code; // No builder class found + } + + int builderClassStart = builderClassMatcher.start(); + + // Find the end of the builder class + int braceCount = 1; + int builderClassEnd = code.indexOf("{", builderClassStart) + 1; + + while (builderClassEnd < code.length() && braceCount > 0) { + char c = code.charAt(builderClassEnd); + if (c == '{') braceCount++; + else if (c == '}') braceCount--; + builderClassEnd++; + } + + if (braceCount != 0) { + return code; // Couldn't find proper end of builder class + } + + String builderClassCode = code.substring(builderClassStart, builderClassEnd); + + // Extract the class name to determine the builder return type + Pattern classNamePattern = Pattern.compile("public class (\\w+)"); + Matcher classNameMatcher = classNamePattern.matcher(code); + if (!classNameMatcher.find()) { + return code; // Can't find class name + } + String className = classNameMatcher.group(1); + + // Extract namespace/package name to correctly reference the builder class + String namespace = ""; + Pattern packagePattern = Pattern.compile("package\\s+([^;]+);"); + Matcher packageMatcher = packagePattern.matcher(code); + if (packageMatcher.find()) { + namespace = packageMatcher.group(1) + "."; + } + + String builderReturnType = namespace + className + ".Builder"; + + // Find numeric setter methods in the builder class + // Pattern to match builder setter methods like: + // public IntsAndLongsWithBuilder.Builder setIntField(int value) { ... } + Pattern builderSetterPattern = Pattern.compile( + "public\\s+[\\w.]+\\.Builder\\s+(\\w+)\\s*\\(\\s*(int|long|java\\.lang\\.Integer|java\\.lang\\.Long)\\s+(\\w+)\\s*\\)\\s*\\{"); + Matcher builderSetterMatcher = builderSetterPattern.matcher(builderClassCode); + + StringBuilder modifiedBuilderClass = new StringBuilder(builderClassCode); + int offset = 0; + + while (builderSetterMatcher.find()) { + String methodName = builderSetterMatcher.group(1); + String paramType = builderSetterMatcher.group(2); + String paramName = builderSetterMatcher.group(3); + + // Only process methods that start with "set" + if (!methodName.startsWith("set")) { + continue; + } + + // Find the end of the original setter method + int methodStart = builderSetterMatcher.start() + offset; + int methodBodyStart = modifiedBuilderClass.indexOf("{", methodStart); + + int methodEnd = methodBodyStart + 1; + braceCount = 1; + + while (methodEnd < modifiedBuilderClass.length() && braceCount > 0) { + char c = modifiedBuilderClass.charAt(methodEnd); + if (c == '{') braceCount++; + else if (c == '}') braceCount--; + methodEnd++; + } + + // Create the overloaded setter based on the parameter type + StringBuilder overloadedSetter = new StringBuilder(); + + if ("int".equals(paramType)) { + // For int parameters, add an overloaded setter that accepts long with bounds checking + overloadedSetter.append("\n\n /**\n"); + overloadedSetter.append(" * Sets ").append(paramName).append(" to the specified value.\n"); + overloadedSetter.append(" * Accepts a long value and converts it to int with bounds checking.\n"); + overloadedSetter.append(" * @param value The long value to set\n"); + overloadedSetter.append(" * @return This builder\n"); + overloadedSetter.append(" * @throws org.apache.avro.AvroRuntimeException if the value is outside the int range\n"); + overloadedSetter.append(" */\n"); + overloadedSetter.append(" public ").append(builderReturnType).append(" ").append(methodName).append("(long value) {\n"); + overloadedSetter.append(" if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); + overloadedSetter.append(" return ").append(methodName).append("((int) value);\n"); + overloadedSetter.append(" } else {\n"); + overloadedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to int\");\n"); + overloadedSetter.append(" }\n"); + overloadedSetter.append(" }"); + } else if ("long".equals(paramType)) { + // For long parameters, add an overloaded setter that accepts int + overloadedSetter.append("\n\n /**\n"); + overloadedSetter.append(" * Sets ").append(paramName).append(" to the specified value.\n"); + overloadedSetter.append(" * Accepts an int value and converts it to long.\n"); + overloadedSetter.append(" * @param value The int value to set\n"); + overloadedSetter.append(" * @return This builder\n"); + overloadedSetter.append(" */\n"); + overloadedSetter.append(" public ").append(builderReturnType).append(" ").append(methodName).append("(int value) {\n"); + overloadedSetter.append(" return ").append(methodName).append("((long) value);\n"); + overloadedSetter.append(" }"); + } else if (JAVA_LANG_INTEGER.equals(paramType)) { + // For Integer parameters, add an overloaded setter that accepts Long with bounds checking + overloadedSetter.append("\n\n /**\n"); + overloadedSetter.append(" * Sets ").append(paramName).append(" to the specified value.\n"); + overloadedSetter.append(" * Accepts a Long value and converts it to Integer with bounds checking.\n"); + overloadedSetter.append(" * @param value The Long value to set\n"); + overloadedSetter.append(" * @return This builder\n"); + overloadedSetter.append(" * @throws org.apache.avro.AvroRuntimeException if the value is outside the Integer range\n"); + overloadedSetter.append(" */\n"); + overloadedSetter.append(" public ").append(builderReturnType).append(" ").append(methodName).append("(java.lang.Long value) {\n"); + overloadedSetter.append(" if (value == null) {\n"); + overloadedSetter.append(" return ").append(methodName).append("((java.lang.Integer) null);\n"); + overloadedSetter.append(" } else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); + overloadedSetter.append(" return ").append(methodName).append("(value.intValue());\n"); + overloadedSetter.append(" } else {\n"); + overloadedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to Integer\");\n"); + overloadedSetter.append(" }\n"); + overloadedSetter.append(" }"); + } else if (JAVA_LANG_LONG.equals(paramType)) { + // For Long parameters, add an overloaded setter that accepts Integer + overloadedSetter.append("\n\n /**\n"); + overloadedSetter.append(" * Sets ").append(paramName).append(" to the specified value.\n"); + overloadedSetter.append(" * Accepts an Integer value and converts it to Long.\n"); + overloadedSetter.append(" * @param value The Integer value to set\n"); + overloadedSetter.append(" * @return This builder\n"); + overloadedSetter.append(" */\n"); + overloadedSetter.append(" public ").append(builderReturnType).append(" ").append(methodName).append("(java.lang.Integer value) {\n"); + overloadedSetter.append(" if (value == null) {\n"); + overloadedSetter.append(" return ").append(methodName).append("((java.lang.Long) null);\n"); + overloadedSetter.append(" } else {\n"); + overloadedSetter.append(" return ").append(methodName).append("(value.longValue());\n"); + overloadedSetter.append(" }\n"); + overloadedSetter.append(" }"); + } + + // Check if the overloaded method already exists in the builder class + String overloadSignature = null; + if ("int".equals(paramType)) { + overloadSignature = "public " + builderReturnType + " " + methodName + "(long "; + } else if ("long".equals(paramType)) { + overloadSignature = "public " + builderReturnType + " " + methodName + "(int "; + } else if (JAVA_LANG_INTEGER.equals(paramType)) { + overloadSignature = "public " + builderReturnType + " " + methodName + "(java.lang.Long "; + } else if (JAVA_LANG_LONG.equals(paramType)) { + overloadSignature = "public " + builderReturnType + " " + methodName + "(java.lang.Integer "; + } + + // Only add if it doesn't already exist + if (overloadSignature != null && !modifiedBuilderClass.toString().contains(overloadSignature)) { + // Insert the overloaded setter after the existing setter + modifiedBuilderClass.insert(methodEnd, overloadedSetter); + offset += overloadedSetter.length(); + } + } + + // Replace the builder class in the original code + return code.substring(0, builderClassStart) + modifiedBuilderClass + code.substring(builderClassEnd); + } + private static String addImports(String code, Collection importStatements) { if (importStatements == null || importStatements.isEmpty()) { return code; From 7d3470d3bd46721cfd74f4d4d7c8cf77adc4b56d Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Thu, 12 Jun 2025 13:31:16 -0700 Subject: [PATCH 12/15] Added helper class Added util class and unit tests testS --- .../CodeTransformationUtils.java | 267 ++++++++++++++++++ .../compatibility/CodeTransformations.java | 252 ++++------------- .../CodeTransformationUtilsTest.java | 169 +++++++++++ 3 files changed, 494 insertions(+), 194 deletions(-) create mode 100644 helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtils.java create mode 100644 helper/helper-common/src/test/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtilsTest.java diff --git a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtils.java b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtils.java new file mode 100644 index 000000000..0371a4e8c --- /dev/null +++ b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtils.java @@ -0,0 +1,267 @@ +/* + * Copyright 2022 LinkedIn Corp. + * Licensed under the BSD 2-Clause License (the "License"). + * See License in the project root for license information. + */ + +package com.linkedin.avroutil1.compatibility; + +public class CodeTransformationUtils { + private CodeTransformationUtils() { + //util class + } + + /** + * Constants for Java numeric type names that are used in multiple places + */ + public static final String JAVA_LANG_INTEGER = "java.lang.Integer"; + public static final String JAVA_LANG_LONG = "java.lang.Long"; + + /** + * Finds the index position of the end of a code block by counting braces. + * Used to find the end of methods, classes, etc. + * + * @param code The source code string + * @param startPosition The position from which to start looking (usually after the opening brace) + * @return The position of the end of the block (after the closing brace) or -1 if the block end can't be found + */ + public static int findEndOfBlock(String code, int startPosition) { + if (startPosition < 0 || startPosition >= code.length()) { + return -1; // Invalid start position + } + + // If we're not starting at a brace, find the first opening brace + int position = startPosition; + if (code.charAt(position) != '{') { + position = code.indexOf('{', position); + if (position == -1) { + return -1; // No opening brace found + } + } + + // Start counting from after the opening brace + position++; + + int braceCount = 1; // We've already seen the opening brace + while (position < code.length() && braceCount > 0) { + char c = code.charAt(position); + if (c == '{') { + braceCount++; + } else if (c == '}') { + braceCount--; + } + position++; + } + + // If brace count is not 0, we didn't find the matching closing brace + return braceCount == 0 ? position : -1; + } + + /** + * Generates Javadoc for an overloaded numeric setter method or builder setter method. + * + * @param fieldName Name of the field being modified + * @param sourceType The type of the parameter (e.g. "long" or "java.lang.Long") + * @param targetType The type of the field (e.g. "int" or "java.lang.Integer") + * @param isBuilderMethod Whether this is for a builder method (returns builder) or a regular setter (returns void) + * @return StringBuilder containing the generated Javadoc + */ + public static StringBuilder generateNumericConversionJavadoc(String fieldName, String sourceType, String targetType, boolean isBuilderMethod) { + StringBuilder javadoc = new StringBuilder("\n\n /**\n"); + javadoc.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); + + // Add description based on source and target types + if ("int".equals(sourceType) || JAVA_LANG_INTEGER.equals(sourceType)) { + javadoc.append(" * Accepts an "); + javadoc.append("int".equals(sourceType) ? "int" : "Integer"); + javadoc.append(" value and converts it to "); + javadoc.append("long".equals(targetType) ? "long" : "Long"); + javadoc.append(".\n"); + } else if ("long".equals(sourceType) || JAVA_LANG_LONG.equals(sourceType)) { + javadoc.append(" * Accepts a "); + javadoc.append("long".equals(sourceType) ? "long" : "Long"); + javadoc.append(" value and converts it to "); + javadoc.append("int".equals(targetType) ? "int" : "Integer"); + javadoc.append(" with bounds checking.\n"); + } + + // Add parameter description + javadoc.append(" * @param value The "); + javadoc.append("int".equals(sourceType) ? "int" : + "long".equals(sourceType) ? "long" : + JAVA_LANG_INTEGER.equals(sourceType) ? "Integer" : "Long"); + javadoc.append(" value to set\n"); + + // Add return value description for builder methods + if (isBuilderMethod) { + javadoc.append(" * @return This builder\n"); + } + + // Add exception for bounds checking (int/Integer target types) + if ("int".equals(targetType) || JAVA_LANG_INTEGER.equals(targetType)) { + javadoc.append(" * @throws org.apache.avro.AvroRuntimeException if the value is outside the "); + javadoc.append("int".equals(targetType) ? "int" : "Integer"); + javadoc.append(" range\n"); + } + + javadoc.append(" */\n"); + return javadoc; + } + + /** + * Generates the method signature for an overloaded numeric setter or builder setter. + * + * @param methodName The name of the method (e.g., "setIntField") + * @param sourceType The source type of the parameter (e.g., "long" or "java.lang.Long") + * @param paramName The name of the parameter (usually "value") + * @param returnType The return type, "void" for regular setters or builder class name for builder setters + * @return StringBuilder containing the generated method signature including the opening brace + */ + public static StringBuilder generateNumericMethodSignature( + String methodName, String sourceType, String paramName, String returnType) { + StringBuilder signature = new StringBuilder(" public "); + signature.append(returnType).append(" ") + .append(methodName).append("(") + .append(sourceType).append(" ") + .append(paramName).append(") {\n"); + return signature; + } + + /** + * Generates code for converting between numeric types with appropriate bounds checking and null handling. + * + * @param fieldName The field name to set + * @param sourceType The source type parameter (e.g., "long", "java.lang.Long") + * @param targetType The target type field (e.g., "int", "java.lang.Integer") + * @param isBuilderMethod Whether this is for a builder method (returns the builder) or regular setter + * @param builderMethodName For builder methods, the name of the method to return to (usually same as methodName) + * @return StringBuilder containing the generated method body without the closing brace + */ + public static StringBuilder generateNumericConversionCode( + String fieldName, String sourceType, String targetType, + boolean isBuilderMethod, String builderMethodName) { + StringBuilder code = new StringBuilder(); + + if ("int".equals(targetType) && "long".equals(sourceType)) { + // Convert long to int with bounds check + code.append(" if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); + if (isBuilderMethod) { + code.append(" return ").append(builderMethodName).append("((int) value);\n"); + } else { + code.append(" this.").append(fieldName).append(" = (int) value;\n"); + } + code.append(" } else {\n"); + code.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to int\");\n"); + code.append(" }"); + } else if ("long".equals(targetType) && "int".equals(sourceType)) { + // Convert int to long + if (isBuilderMethod) { + code.append(" return ").append(builderMethodName).append("((long) value);"); + } else { + code.append(" this.").append(fieldName).append(" = (long) value;"); + } + } else if (JAVA_LANG_INTEGER.equals(targetType) && JAVA_LANG_LONG.equals(sourceType)) { + // Convert Long to Integer with bounds check and null handling + code.append(" if (value == null) {\n"); + if (isBuilderMethod) { + code.append(" return ").append(builderMethodName).append("((").append(JAVA_LANG_INTEGER).append(") null);\n"); + } else { + code.append(" this.").append(fieldName).append(" = null;\n"); + } + code.append(" } else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); + if (isBuilderMethod) { + code.append(" return ").append(builderMethodName).append("(value.intValue());\n"); + } else { + code.append(" this.").append(fieldName).append(" = value.intValue();\n"); + } + code.append(" } else {\n"); + code.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to Integer\");\n"); + code.append(" }"); + } else if (JAVA_LANG_LONG.equals(targetType) && JAVA_LANG_INTEGER.equals(sourceType)) { + // Convert Integer to Long with null handling + code.append(" if (value == null) {\n"); + if (isBuilderMethod) { + code.append(" return ").append(builderMethodName).append("((").append(JAVA_LANG_LONG).append(") null);\n"); + } else { + code.append(" this.").append(fieldName).append(" = null;\n"); + } + code.append(" } else {\n"); + if (isBuilderMethod) { + code.append(" return ").append(builderMethodName).append("(value.longValue());\n"); + } else { + code.append(" this.").append(fieldName).append(" = value.longValue();\n"); + } + code.append(" }"); + } + + return code; + } + + /** + * Determines the appropriate overloaded method signature for a given field type. + * + * @param fieldType The type of the field (e.g., "int", "java.lang.Integer") + * @param methodName The setter method name + * @return The signature of the overloaded method, or null if no overloaded method is needed + */ + public static String determineOverloadSignature(String fieldType, String methodName) { + String overloadSignature = null; + if ("int".equals(fieldType)) { + overloadSignature = "public void " + methodName + "(long "; + } else if ("long".equals(fieldType)) { + overloadSignature = "public void " + methodName + "(int "; + } else if (JAVA_LANG_INTEGER.equals(fieldType)) { + overloadSignature = "public void " + methodName + "(java.lang.Long "; + } else if (JAVA_LANG_LONG.equals(fieldType)) { + overloadSignature = "public void " + methodName + "(java.lang.Integer "; + } + return overloadSignature; + } + + /** + * Generates the complete code for an overloaded setter method with proper type conversion. + * + * @param methodName The name of the setter method + * @param fieldName The name of the field being set + * @param fieldType The type of the field (e.g., "int", "java.lang.Integer") + * @param isBuilderMethod Whether this is for a builder method + * @param builderReturnType If isBuilderMethod is true, the return type for the builder method + * @return Complete code for the overloaded setter method + */ + public static StringBuilder generateOverloadedSetter( + String methodName, String fieldName, String fieldType, + boolean isBuilderMethod, String builderReturnType) { + + StringBuilder overloadedSetter = new StringBuilder(); + String returnType = isBuilderMethod ? builderReturnType : "void"; + String sourceType; + String targetType = fieldType; + + // Determine the appropriate source type based on the target field type + if ("int".equals(fieldType)) { + sourceType = "long"; + } else if ("long".equals(fieldType)) { + sourceType = "int"; + } else if (JAVA_LANG_INTEGER.equals(fieldType)) { + sourceType = JAVA_LANG_LONG; + } else if (JAVA_LANG_LONG.equals(fieldType)) { + sourceType = JAVA_LANG_INTEGER; + } else { + return overloadedSetter; // Empty if not a numeric type we handle + } + + // Generate the method Javadoc + overloadedSetter.append(generateNumericConversionJavadoc(fieldName, sourceType, targetType, isBuilderMethod)); + + // Generate the method signature + overloadedSetter.append(generateNumericMethodSignature(methodName, sourceType, "value", returnType)); + + // Generate the conversion code + overloadedSetter.append(generateNumericConversionCode(fieldName, sourceType, targetType, isBuilderMethod, methodName)); + + // Close the method + overloadedSetter.append("\n }"); + + return overloadedSetter; + } +} diff --git a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java index a4d178b82..55739fdde 100644 --- a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java +++ b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java @@ -6,8 +6,6 @@ package com.linkedin.avroutil1.compatibility; -import org.apache.commons.text.StringEscapeUtils; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -19,6 +17,8 @@ import java.util.StringJoiner; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.text.StringEscapeUtils; + /** * utility class for transforming avro-generated java code @@ -105,9 +105,6 @@ public class CodeTransformations { "([^}]+)" + // Capture the case statements "\\}\\s*\\}"); - private static final String JAVA_LANG_INTEGER = "java.lang.Integer"; - private static final String JAVA_LANG_LONG = "java.lang.Long"; - private static final int MAX_STRING_LITERAL_SIZE = 65000; //just under 64k private CodeTransformations() { @@ -967,8 +964,8 @@ public static String enhanceNumericPutMethod(String code) { String fieldName = caseMatcher.group(2); String fieldType = caseMatcher.group(3); - if (JAVA_LANG_INTEGER.equals(fieldType) || "int".equals(fieldType) || - JAVA_LANG_LONG.equals(fieldType) || "long".equals(fieldType)) { + if (CodeTransformationUtils.JAVA_LANG_INTEGER.equals(fieldType) || "int".equals(fieldType) || + CodeTransformationUtils.JAVA_LANG_LONG.equals(fieldType) || "long".equals(fieldType)) { fieldInfo.put(caseNumber, new String[]{fieldName, fieldType}); } } @@ -1015,7 +1012,7 @@ public static String enhanceNumericPutMethod(String code) { enhancedPutMethod.append(" this.").append(fieldName).append(" = (java.lang.Long)value$;\n"); enhancedPutMethod.append(" }\n"); enhancedPutMethod.append(" break;\n"); - } else if (JAVA_LANG_INTEGER.equals(fieldType)) { + } else if (CodeTransformationUtils.JAVA_LANG_INTEGER.equals(fieldType)) { enhancedPutMethod.append("if (value$ instanceof java.lang.Long) {\n"); enhancedPutMethod.append(" if ((java.lang.Long)value$ <= Integer.MAX_VALUE && (java.lang.Long)value$ >= Integer.MIN_VALUE) {\n"); enhancedPutMethod.append(" this.").append(fieldName).append(" = ((java.lang.Long)value$).intValue();\n"); @@ -1026,7 +1023,7 @@ public static String enhanceNumericPutMethod(String code) { enhancedPutMethod.append(" this.").append(fieldName).append(" = (java.lang.Integer)value$;\n"); enhancedPutMethod.append(" }\n"); enhancedPutMethod.append(" break;\n"); - } else if (JAVA_LANG_LONG.equals(fieldType)) { + } else if (CodeTransformationUtils.JAVA_LANG_LONG.equals(fieldType)) { enhancedPutMethod.append("if (value$ instanceof java.lang.Integer) {\n"); enhancedPutMethod.append(" this.").append(fieldName).append(" = ((java.lang.Integer)value$).longValue();\n"); enhancedPutMethod.append(" } else {\n"); @@ -1119,93 +1116,23 @@ public static String addOverloadedNumericSetterMethods(String code) { continue; // Setter not found } - // Find the end of the setter method (closing brace) - int braceCount = 1; - int setterEnd = code.indexOf("{", setterStart); - if (setterEnd == -1) continue; - - setterEnd++; - while (setterEnd < code.length() && braceCount > 0) { - char c = code.charAt(setterEnd); - if (c == '{') braceCount++; - else if (c == '}') braceCount--; - setterEnd++; - } + // Find the end of the setter method using the helper method + int setterBodyStart = code.indexOf("{", setterStart); + if (setterBodyStart == -1) continue; + + int setterEnd = CodeTransformationUtils.findEndOfBlock(code, setterBodyStart); + if (setterEnd == -1) continue; // Couldn't find proper end of method + + // Generate the overloaded setter using the utility method + StringBuilder overloadedSetter = CodeTransformationUtils.generateOverloadedSetter( + methodName, fieldName, fieldType, false, null); - if (braceCount != 0) continue; // Couldn't find proper end of method - - StringBuilder overloadedSetter = new StringBuilder(); - - // Add the appropriate overloaded setter based on field type - if ("int".equals(fieldType)) { - // For int fields, add an overloaded setter that accepts long with bounds checking - overloadedSetter.append("\n\n /**\n"); - overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); - overloadedSetter.append(" * Accepts a long value and converts it to int with bounds checking.\n"); - overloadedSetter.append(" * @param value The long value to set\n"); - overloadedSetter.append(" * @throws org.apache.avro.AvroRuntimeException if the value is outside the int range\n"); - overloadedSetter.append(" */\n"); - overloadedSetter.append(" public void ").append(methodName).append("(long value) {\n"); - overloadedSetter.append(" if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); - overloadedSetter.append(" this.").append(fieldName).append(" = (int) value;\n"); - overloadedSetter.append(" } else {\n"); - overloadedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to int\");\n"); - overloadedSetter.append(" }\n"); - overloadedSetter.append(" }"); - } else if ("long".equals(fieldType)) { - // For long fields, add an overloaded setter that accepts int - overloadedSetter.append("\n\n /**\n"); - overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); - overloadedSetter.append(" * Accepts an int value and converts it to long.\n"); - overloadedSetter.append(" * @param value The int value to set\n"); - overloadedSetter.append(" */\n"); - overloadedSetter.append(" public void ").append(methodName).append("(int value) {\n"); - overloadedSetter.append(" this.").append(fieldName).append(" = (long) value;\n"); - overloadedSetter.append(" }"); - } else if (JAVA_LANG_INTEGER.equals(fieldType)) { - // For Integer fields, add an overloaded setter that accepts Long with bounds checking - overloadedSetter.append("\n\n /**\n"); - overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); - overloadedSetter.append(" * Accepts a Long value and converts it to Integer with bounds checking.\n"); - overloadedSetter.append(" * @param value The Long value to set\n"); - overloadedSetter.append(" * @throws org.apache.avro.AvroRuntimeException if the value is outside the Integer range\n"); - overloadedSetter.append(" */\n"); - overloadedSetter.append(" public void ").append(methodName).append("(java.lang.Long value) {\n"); - overloadedSetter.append(" if (value == null) {\n"); - overloadedSetter.append(" this.").append(fieldName).append(" = null;\n"); - overloadedSetter.append(" } else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); - overloadedSetter.append(" this.").append(fieldName).append(" = value.intValue();\n"); - overloadedSetter.append(" } else {\n"); - overloadedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to Integer\");\n"); - overloadedSetter.append(" }\n"); - overloadedSetter.append(" }"); - } else if (JAVA_LANG_LONG.equals(fieldType)) { - // For Long fields, add an overloaded setter that accepts Integer - overloadedSetter.append("\n\n /**\n"); - overloadedSetter.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); - overloadedSetter.append(" * Accepts an Integer value and converts it to Long.\n"); - overloadedSetter.append(" * @param value The Integer value to set\n"); - overloadedSetter.append(" */\n"); - overloadedSetter.append(" public void ").append(methodName).append("(java.lang.Integer value) {\n"); - overloadedSetter.append(" if (value == null) {\n"); - overloadedSetter.append(" this.").append(fieldName).append(" = null;\n"); - overloadedSetter.append(" } else {\n"); - overloadedSetter.append(" this.").append(fieldName).append(" = value.longValue();\n"); - overloadedSetter.append(" }\n"); - overloadedSetter.append(" }"); + if (overloadedSetter.length() == 0) { + continue; // Not a numeric type we handle } // Check if the overloaded method already exists in the code - String overloadSignature = null; - if ("int".equals(fieldType)) { - overloadSignature = "public void " + methodName + "(long "; - } else if ("long".equals(fieldType)) { - overloadSignature = "public void " + methodName + "(int "; - } else if (JAVA_LANG_INTEGER.equals(fieldType)) { - overloadSignature = "public void " + methodName + "(java.lang.Long "; - } else if (JAVA_LANG_LONG.equals(fieldType)) { - overloadSignature = "public void " + methodName + "(java.lang.Integer "; - } + String overloadSignature = CodeTransformationUtils.determineOverloadSignature(fieldType, methodName); // Only add if it doesn't already exist if (overloadSignature != null && !code.contains(overloadSignature)) { @@ -1336,12 +1263,12 @@ public static String addOverloadedNumericConstructor(String code) { String paramName = paramNames.get(i); // Swap Java types for numeric fields - if (JAVA_LANG_LONG.equals(paramType) && fieldTypes.containsKey(paramName)) { - overloadedConstructor.append(JAVA_LANG_INTEGER + " ").append(paramName); - swappedParamTypes.put(paramName, JAVA_LANG_INTEGER); - } else if (JAVA_LANG_INTEGER.equals(paramType) && fieldTypes.containsKey(paramName)) { - overloadedConstructor.append(JAVA_LANG_LONG + " ").append(paramName); - swappedParamTypes.put(paramName, JAVA_LANG_LONG); + if (CodeTransformationUtils.JAVA_LANG_LONG.equals(paramType) && fieldTypes.containsKey(paramName)) { + overloadedConstructor.append(CodeTransformationUtils.JAVA_LANG_INTEGER + " ").append(paramName); + swappedParamTypes.put(paramName, CodeTransformationUtils.JAVA_LANG_INTEGER); + } else if (CodeTransformationUtils.JAVA_LANG_INTEGER.equals(paramType) && fieldTypes.containsKey(paramName)) { + overloadedConstructor.append(CodeTransformationUtils.JAVA_LANG_LONG + " ").append(paramName); + swappedParamTypes.put(paramName, CodeTransformationUtils.JAVA_LANG_LONG); } else { overloadedConstructor.append(paramType).append(" ").append(paramName); swappedParamTypes.put(paramName, paramType); @@ -1357,7 +1284,7 @@ public static String addOverloadedNumericConstructor(String code) { String swappedParamType = swappedParamTypes.get(paramName); if (fieldType != null) { - if ("int".equals(fieldType) && JAVA_LANG_LONG.equals(swappedParamType)) { + if ("int".equals(fieldType) && CodeTransformationUtils.JAVA_LANG_LONG.equals(swappedParamType)) { // Convert Long to int with bounds check overloadedConstructor.append(" if (").append(paramName).append(" == null) {\n"); overloadedConstructor.append(" this.").append(paramName).append(" = 0;\n"); @@ -1368,11 +1295,12 @@ public static String addOverloadedNumericConstructor(String code) { overloadedConstructor.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + ") .append(paramName).append(" + \" cannot be cast to int\");\n"); overloadedConstructor.append(" }\n"); - } else if ("long".equals(fieldType) && JAVA_LANG_INTEGER.equals(swappedParamType)) { + } else if ("long".equals(fieldType) && CodeTransformationUtils.JAVA_LANG_INTEGER.equals(swappedParamType)) { // Convert Integer to long overloadedConstructor.append(" this.").append(paramName).append(" = ").append(paramName).append(" == null ? 0L : ") .append(paramName).append(".longValue();\n"); - } else if (JAVA_LANG_INTEGER.equals(fieldType) && JAVA_LANG_LONG.equals(swappedParamType)) { + } else if (CodeTransformationUtils.JAVA_LANG_INTEGER.equals(fieldType) + && CodeTransformationUtils.JAVA_LANG_LONG.equals(swappedParamType)) { // Convert Long to Integer with bounds check overloadedConstructor.append(" if (").append(paramName).append(" == null) {\n"); overloadedConstructor.append(" this.").append(paramName).append(" = null;\n"); @@ -1383,7 +1311,8 @@ public static String addOverloadedNumericConstructor(String code) { overloadedConstructor.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + ") .append(paramName).append(" + \" cannot be cast to Integer\");\n"); overloadedConstructor.append(" }\n"); - } else if (JAVA_LANG_LONG.equals(fieldType) && JAVA_LANG_INTEGER.equals(swappedParamType)) { + } else if (CodeTransformationUtils.JAVA_LANG_LONG.equals(fieldType) + && CodeTransformationUtils.JAVA_LANG_INTEGER.equals(swappedParamType)) { // Convert Integer to Long overloadedConstructor.append(" if (").append(paramName).append(" == null) {\n"); overloadedConstructor.append(" this.").append(paramName).append(" = null;\n"); @@ -1506,19 +1435,16 @@ public static String addOverloadedNumericBuilderSetterMethods(String code) { } int builderClassStart = builderClassMatcher.start(); - - // Find the end of the builder class - int braceCount = 1; - int builderClassEnd = code.indexOf("{", builderClassStart) + 1; - - while (builderClassEnd < code.length() && braceCount > 0) { - char c = code.charAt(builderClassEnd); - if (c == '{') braceCount++; - else if (c == '}') braceCount--; - builderClassEnd++; - } - - if (braceCount != 0) { + + // Find the opening brace of the builder class + int builderClassOpenBrace = code.indexOf("{", builderClassStart); + if (builderClassOpenBrace == -1) { + return code; // Unexpected format + } + + // Find the end of the builder class using the helper method + int builderClassEnd = CodeTransformationUtils.findEndOfBlock(code, builderClassOpenBrace); + if (builderClassEnd == -1) { return code; // Couldn't find proper end of builder class } @@ -1543,8 +1469,6 @@ public static String addOverloadedNumericBuilderSetterMethods(String code) { String builderReturnType = namespace + className + ".Builder"; // Find numeric setter methods in the builder class - // Pattern to match builder setter methods like: - // public IntsAndLongsWithBuilder.Builder setIntField(int value) { ... } Pattern builderSetterPattern = Pattern.compile( "public\\s+[\\w.]+\\.Builder\\s+(\\w+)\\s*\\(\\s*(int|long|java\\.lang\\.Integer|java\\.lang\\.Long)\\s+(\\w+)\\s*\\)\\s*\\{"); Matcher builderSetterMatcher = builderSetterPattern.matcher(builderClassCode); @@ -1562,83 +1486,23 @@ public static String addOverloadedNumericBuilderSetterMethods(String code) { continue; } - // Find the end of the original setter method + // Find the position of the method body start int methodStart = builderSetterMatcher.start() + offset; int methodBodyStart = modifiedBuilderClass.indexOf("{", methodStart); - - int methodEnd = methodBodyStart + 1; - braceCount = 1; - - while (methodEnd < modifiedBuilderClass.length() && braceCount > 0) { - char c = modifiedBuilderClass.charAt(methodEnd); - if (c == '{') braceCount++; - else if (c == '}') braceCount--; - methodEnd++; - } - - // Create the overloaded setter based on the parameter type - StringBuilder overloadedSetter = new StringBuilder(); - - if ("int".equals(paramType)) { - // For int parameters, add an overloaded setter that accepts long with bounds checking - overloadedSetter.append("\n\n /**\n"); - overloadedSetter.append(" * Sets ").append(paramName).append(" to the specified value.\n"); - overloadedSetter.append(" * Accepts a long value and converts it to int with bounds checking.\n"); - overloadedSetter.append(" * @param value The long value to set\n"); - overloadedSetter.append(" * @return This builder\n"); - overloadedSetter.append(" * @throws org.apache.avro.AvroRuntimeException if the value is outside the int range\n"); - overloadedSetter.append(" */\n"); - overloadedSetter.append(" public ").append(builderReturnType).append(" ").append(methodName).append("(long value) {\n"); - overloadedSetter.append(" if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); - overloadedSetter.append(" return ").append(methodName).append("((int) value);\n"); - overloadedSetter.append(" } else {\n"); - overloadedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to int\");\n"); - overloadedSetter.append(" }\n"); - overloadedSetter.append(" }"); - } else if ("long".equals(paramType)) { - // For long parameters, add an overloaded setter that accepts int - overloadedSetter.append("\n\n /**\n"); - overloadedSetter.append(" * Sets ").append(paramName).append(" to the specified value.\n"); - overloadedSetter.append(" * Accepts an int value and converts it to long.\n"); - overloadedSetter.append(" * @param value The int value to set\n"); - overloadedSetter.append(" * @return This builder\n"); - overloadedSetter.append(" */\n"); - overloadedSetter.append(" public ").append(builderReturnType).append(" ").append(methodName).append("(int value) {\n"); - overloadedSetter.append(" return ").append(methodName).append("((long) value);\n"); - overloadedSetter.append(" }"); - } else if (JAVA_LANG_INTEGER.equals(paramType)) { - // For Integer parameters, add an overloaded setter that accepts Long with bounds checking - overloadedSetter.append("\n\n /**\n"); - overloadedSetter.append(" * Sets ").append(paramName).append(" to the specified value.\n"); - overloadedSetter.append(" * Accepts a Long value and converts it to Integer with bounds checking.\n"); - overloadedSetter.append(" * @param value The Long value to set\n"); - overloadedSetter.append(" * @return This builder\n"); - overloadedSetter.append(" * @throws org.apache.avro.AvroRuntimeException if the value is outside the Integer range\n"); - overloadedSetter.append(" */\n"); - overloadedSetter.append(" public ").append(builderReturnType).append(" ").append(methodName).append("(java.lang.Long value) {\n"); - overloadedSetter.append(" if (value == null) {\n"); - overloadedSetter.append(" return ").append(methodName).append("((java.lang.Integer) null);\n"); - overloadedSetter.append(" } else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); - overloadedSetter.append(" return ").append(methodName).append("(value.intValue());\n"); - overloadedSetter.append(" } else {\n"); - overloadedSetter.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to Integer\");\n"); - overloadedSetter.append(" }\n"); - overloadedSetter.append(" }"); - } else if (JAVA_LANG_LONG.equals(paramType)) { - // For Long parameters, add an overloaded setter that accepts Integer - overloadedSetter.append("\n\n /**\n"); - overloadedSetter.append(" * Sets ").append(paramName).append(" to the specified value.\n"); - overloadedSetter.append(" * Accepts an Integer value and converts it to Long.\n"); - overloadedSetter.append(" * @param value The Integer value to set\n"); - overloadedSetter.append(" * @return This builder\n"); - overloadedSetter.append(" */\n"); - overloadedSetter.append(" public ").append(builderReturnType).append(" ").append(methodName).append("(java.lang.Integer value) {\n"); - overloadedSetter.append(" if (value == null) {\n"); - overloadedSetter.append(" return ").append(methodName).append("((java.lang.Long) null);\n"); - overloadedSetter.append(" } else {\n"); - overloadedSetter.append(" return ").append(methodName).append("(value.longValue());\n"); - overloadedSetter.append(" }\n"); - overloadedSetter.append(" }"); + if (methodBodyStart == -1) continue; + + // Find the end of the method using the helper + int methodRelativeEnd = CodeTransformationUtils.findEndOfBlock(modifiedBuilderClass.toString(), methodBodyStart); + if (methodRelativeEnd == -1) continue; // Couldn't find proper end of method + + int methodEnd = methodRelativeEnd; + + // Generate the overloaded setter using the utility method + StringBuilder overloadedSetter = CodeTransformationUtils.generateOverloadedSetter( + methodName, paramName, paramType, true, builderReturnType); + + if (overloadedSetter.length() == 0) { + continue; // Not a numeric type we handle } // Check if the overloaded method already exists in the builder class @@ -1647,9 +1511,9 @@ public static String addOverloadedNumericBuilderSetterMethods(String code) { overloadSignature = "public " + builderReturnType + " " + methodName + "(long "; } else if ("long".equals(paramType)) { overloadSignature = "public " + builderReturnType + " " + methodName + "(int "; - } else if (JAVA_LANG_INTEGER.equals(paramType)) { + } else if (CodeTransformationUtils.JAVA_LANG_INTEGER.equals(paramType)) { overloadSignature = "public " + builderReturnType + " " + methodName + "(java.lang.Long "; - } else if (JAVA_LANG_LONG.equals(paramType)) { + } else if (CodeTransformationUtils.JAVA_LANG_LONG.equals(paramType)) { overloadSignature = "public " + builderReturnType + " " + methodName + "(java.lang.Integer "; } diff --git a/helper/helper-common/src/test/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtilsTest.java b/helper/helper-common/src/test/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtilsTest.java new file mode 100644 index 000000000..a0eaab355 --- /dev/null +++ b/helper/helper-common/src/test/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtilsTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 2020 LinkedIn Corp. + * Licensed under the BSD 2-Clause License (the "License"). + * See License in the project root for license information. + */ + +package com.linkedin.avroutil1.compatibility; + +import org.testng.Assert; +import org.testng.annotations.Test; + + +public class CodeTransformationUtilsTest { + + @Test + public void testFindEndOfBlock() { + // Test basic code block + String code1 = "public void method() { int x = 1; return; }"; + int result1 = CodeTransformationUtils.findEndOfBlock(code1, code1.indexOf('{')); + Assert.assertEquals(result1, code1.length()); + + // Test nested blocks + String code2 = "public void method() { if (true) { int x = 1; } return; }"; + int result2 = CodeTransformationUtils.findEndOfBlock(code2, code2.indexOf('{')); + Assert.assertEquals(result2, code2.length()); + + // Test multiple nested blocks + String code3 = "public void method() { if (true) { while(x < 10) { x++; } } return; }"; + int result3 = CodeTransformationUtils.findEndOfBlock(code3, code3.indexOf('{')); + Assert.assertEquals(result3, code3.length()); + + // Test invalid start position + int result4 = CodeTransformationUtils.findEndOfBlock("public void method() {}", -1); + Assert.assertEquals(result4, -1); + + // Test no opening brace + int result5 = CodeTransformationUtils.findEndOfBlock("public void method()", 0); + Assert.assertEquals(result5, -1); + + // Test unclosed block + String code6 = "public void method() { if (true) {"; + int result6 = CodeTransformationUtils.findEndOfBlock(code6, code6.indexOf('{')); + Assert.assertEquals(result6, -1); + + // Test second block in code + String code7 = "public void method1() {} public void method2() { return; }"; + int firstBraceIndex = code7.indexOf('{'); + int secondBraceIndex = code7.indexOf('{', firstBraceIndex + 1); + int result7 = CodeTransformationUtils.findEndOfBlock(code7, secondBraceIndex); + Assert.assertEquals(result7, code7.length()); + } + + @Test + public void testGenerateNumericConversionJavadoc() { + // Test Integer to Long conversion (regular setter) + StringBuilder result1 = CodeTransformationUtils.generateNumericConversionJavadoc( + "count", "java.lang.Integer", "java.lang.Long", false); + Assert.assertTrue(result1.toString().contains("Sets count to the specified value")); + Assert.assertTrue(result1.toString().contains("Accepts an Integer value and converts it to Long")); + Assert.assertTrue(result1.toString().contains("@param value The Integer value to set")); + Assert.assertFalse(result1.toString().contains("@return")); + + // Test long to int conversion (builder method) + StringBuilder result2 = CodeTransformationUtils.generateNumericConversionJavadoc( + "age", "long", "int", true); + Assert.assertTrue(result2.toString().contains("Sets age to the specified value")); + Assert.assertTrue(result2.toString().contains("Accepts a long value and converts it to int with bounds checking")); + Assert.assertTrue(result2.toString().contains("@param value The long value to set")); + Assert.assertTrue(result2.toString().contains("@return This builder")); + Assert.assertTrue(result2.toString().contains("@throws org.apache.avro.AvroRuntimeException")); + } + + @Test + public void testGenerateNumericMethodSignature() { + // Test regular setter signature + StringBuilder sig1 = CodeTransformationUtils.generateNumericMethodSignature( + "setValue", "long", "value", "void"); + Assert.assertEquals(sig1.toString(), " public void setValue(long value) {\n"); + + // Test builder method signature + StringBuilder sig2 = CodeTransformationUtils.generateNumericMethodSignature( + "withCount", "java.lang.Integer", "value", "Builder"); + Assert.assertEquals(sig2.toString(), " public Builder withCount(java.lang.Integer value) {\n"); + } + + @Test + public void testGenerateNumericConversionCode() { + // Test long to int conversion (regular setter) + StringBuilder code1 = CodeTransformationUtils.generateNumericConversionCode( + "count", "long", "int", false, null); + Assert.assertTrue(code1.toString().contains("if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE)")); + Assert.assertTrue(code1.toString().contains("this.count = (int) value")); + Assert.assertTrue(code1.toString().contains("throw new org.apache.avro.AvroRuntimeException")); + + // Test int to long conversion (builder method) + StringBuilder code2 = CodeTransformationUtils.generateNumericConversionCode( + "size", "int", "long", true, "withSize"); + Assert.assertTrue(code2.toString().contains("return withSize((long) value)")); + + // Test Long to Integer conversion (regular setter with null handling) + StringBuilder code3 = CodeTransformationUtils.generateNumericConversionCode( + "count", "java.lang.Long", "java.lang.Integer", false, null); + Assert.assertTrue(code3.toString().contains("if (value == null)")); + Assert.assertTrue(code3.toString().contains("this.count = null")); + Assert.assertTrue(code3.toString().contains("this.count = value.intValue()")); + + // Test Integer to Long conversion (builder method with null handling) + StringBuilder code4 = CodeTransformationUtils.generateNumericConversionCode( + "size", "java.lang.Integer", "java.lang.Long", true, "withSize"); + Assert.assertTrue(code4.toString().contains("if (value == null)")); + Assert.assertTrue(code4.toString().contains("return withSize((java.lang.Long) null)")); + Assert.assertTrue(code4.toString().contains("return withSize(value.longValue())")); + } + + @Test + public void testDetermineOverloadSignature() { + // Test primitive types + Assert.assertEquals(CodeTransformationUtils.determineOverloadSignature("int", "setValue"), + "public void setValue(long "); + Assert.assertEquals(CodeTransformationUtils.determineOverloadSignature("long", "setValue"), + "public void setValue(int "); + + // Test wrapper types + Assert.assertEquals(CodeTransformationUtils.determineOverloadSignature("java.lang.Integer", "setValue"), + "public void setValue(java.lang.Long "); + Assert.assertEquals(CodeTransformationUtils.determineOverloadSignature("java.lang.Long", "setValue"), + "public void setValue(java.lang.Integer "); + + // Test unsupported type + Assert.assertNull(CodeTransformationUtils.determineOverloadSignature("String", "setValue")); + } + + @Test + public void testGenerateOverloadedSetter() { + // Test primitive int field with regular setter + StringBuilder setter1 = CodeTransformationUtils.generateOverloadedSetter( + "setCount", "count", "int", false, null); + Assert.assertTrue(setter1.toString().contains("public void setCount(long value)")); + Assert.assertTrue(setter1.toString().contains("if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE)")); + Assert.assertTrue(setter1.toString().contains("this.count = (int) value")); + + // Test primitive long field with builder method + StringBuilder setter2 = CodeTransformationUtils.generateOverloadedSetter( + "withSize", "size", "long", true, "Builder"); + Assert.assertTrue(setter2.toString().contains("public Builder withSize(int value)")); + Assert.assertTrue(setter2.toString().contains("return withSize((long) value)")); + + // Test wrapper Integer field with regular setter + StringBuilder setter3 = CodeTransformationUtils.generateOverloadedSetter( + "setCount", "count", "java.lang.Integer", false, null); + Assert.assertTrue(setter3.toString().contains("public void setCount(java.lang.Long value)")); + Assert.assertTrue(setter3.toString().contains("if (value == null)")); + Assert.assertTrue(setter3.toString().contains("this.count = null")); + Assert.assertTrue(setter3.toString().contains("this.count = value.intValue()")); + + // Test wrapper Long field with builder method + StringBuilder setter4 = CodeTransformationUtils.generateOverloadedSetter( + "withSize", "size", "java.lang.Long", true, "Builder"); + Assert.assertTrue(setter4.toString().contains("public Builder withSize(java.lang.Integer value)")); + Assert.assertTrue(setter4.toString().contains("if (value == null)")); + Assert.assertTrue(setter4.toString().contains("return withSize((java.lang.Long) null)")); + Assert.assertTrue(setter4.toString().contains("return withSize(value.longValue())")); + + // Test unsupported type (should return empty StringBuilder) + StringBuilder setter5 = CodeTransformationUtils.generateOverloadedSetter( + "setName", "name", "String", false, null); + Assert.assertEquals(setter5.length(), 0); + } +} From 4e978d29d150e5e01a46a70ef7199375a38e3ad9 Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Thu, 12 Jun 2025 15:42:45 -0700 Subject: [PATCH 13/15] Updated tests, improved overloaded setter logic --- .../CodeTransformationUtils.java | 78 +++++------ .../compatibility/CodeTransformations.java | 123 ++++++++---------- .../CodeTransformationUtilsTest.java | 62 +++++---- .../avro14/CodeTransformationsAvro14Test.java | 58 --------- .../avro15/CodeTransformationsAvro15Test.java | 58 --------- 5 files changed, 120 insertions(+), 259 deletions(-) diff --git a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtils.java b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtils.java index 0371a4e8c..ce5a6c721 100644 --- a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtils.java +++ b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtils.java @@ -16,7 +16,7 @@ private CodeTransformationUtils() { */ public static final String JAVA_LANG_INTEGER = "java.lang.Integer"; public static final String JAVA_LANG_LONG = "java.lang.Long"; - + /** * Finds the index position of the end of a code block by counting braces. * Used to find the end of methods, classes, etc. @@ -29,7 +29,7 @@ public static int findEndOfBlock(String code, int startPosition) { if (startPosition < 0 || startPosition >= code.length()) { return -1; // Invalid start position } - + // If we're not starting at a brace, find the first opening brace int position = startPosition; if (code.charAt(position) != '{') { @@ -38,10 +38,10 @@ public static int findEndOfBlock(String code, int startPosition) { return -1; // No opening brace found } } - + // Start counting from after the opening brace position++; - + int braceCount = 1; // We've already seen the opening brace while (position < code.length() && braceCount > 0) { char c = code.charAt(position); @@ -52,11 +52,11 @@ public static int findEndOfBlock(String code, int startPosition) { } position++; } - + // If brace count is not 0, we didn't find the matching closing brace return braceCount == 0 ? position : -1; } - + /** * Generates Javadoc for an overloaded numeric setter method or builder setter method. * @@ -69,7 +69,7 @@ public static int findEndOfBlock(String code, int startPosition) { public static StringBuilder generateNumericConversionJavadoc(String fieldName, String sourceType, String targetType, boolean isBuilderMethod) { StringBuilder javadoc = new StringBuilder("\n\n /**\n"); javadoc.append(" * Sets ").append(fieldName).append(" to the specified value.\n"); - + // Add description based on source and target types if ("int".equals(sourceType) || JAVA_LANG_INTEGER.equals(sourceType)) { javadoc.append(" * Accepts an "); @@ -84,30 +84,30 @@ public static StringBuilder generateNumericConversionJavadoc(String fieldName, S javadoc.append("int".equals(targetType) ? "int" : "Integer"); javadoc.append(" with bounds checking.\n"); } - + // Add parameter description javadoc.append(" * @param value The "); - javadoc.append("int".equals(sourceType) ? "int" : - "long".equals(sourceType) ? "long" : + javadoc.append("int".equals(sourceType) ? "int" : + "long".equals(sourceType) ? "long" : JAVA_LANG_INTEGER.equals(sourceType) ? "Integer" : "Long"); javadoc.append(" value to set\n"); - + // Add return value description for builder methods if (isBuilderMethod) { javadoc.append(" * @return This builder\n"); } - + // Add exception for bounds checking (int/Integer target types) if ("int".equals(targetType) || JAVA_LANG_INTEGER.equals(targetType)) { javadoc.append(" * @throws org.apache.avro.AvroRuntimeException if the value is outside the "); javadoc.append("int".equals(targetType) ? "int" : "Integer"); javadoc.append(" range\n"); } - + javadoc.append(" */\n"); return javadoc; } - + /** * Generates the method signature for an overloaded numeric setter or builder setter. * @@ -126,29 +126,29 @@ public static StringBuilder generateNumericMethodSignature( .append(paramName).append(") {\n"); return signature; } - + /** * Generates code for converting between numeric types with appropriate bounds checking and null handling. - * - * @param fieldName The field name to set + * * @param sourceType The source type parameter (e.g., "long", "java.lang.Long") * @param targetType The target type field (e.g., "int", "java.lang.Integer") * @param isBuilderMethod Whether this is for a builder method (returns the builder) or regular setter + * @param methodName The name of the original setter method (used for delegating to original setter) * @param builderMethodName For builder methods, the name of the method to return to (usually same as methodName) * @return StringBuilder containing the generated method body without the closing brace */ public static StringBuilder generateNumericConversionCode( - String fieldName, String sourceType, String targetType, - boolean isBuilderMethod, String builderMethodName) { + String sourceType, String targetType, + boolean isBuilderMethod, String methodName, String builderMethodName) { StringBuilder code = new StringBuilder(); - + if ("int".equals(targetType) && "long".equals(sourceType)) { // Convert long to int with bounds check code.append(" if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); if (isBuilderMethod) { code.append(" return ").append(builderMethodName).append("((int) value);\n"); } else { - code.append(" this.").append(fieldName).append(" = (int) value;\n"); + code.append(" ").append(methodName).append("((int) value);\n"); } code.append(" } else {\n"); code.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to int\");\n"); @@ -158,7 +158,7 @@ public static StringBuilder generateNumericConversionCode( if (isBuilderMethod) { code.append(" return ").append(builderMethodName).append("((long) value);"); } else { - code.append(" this.").append(fieldName).append(" = (long) value;"); + code.append(" ").append(methodName).append("((long) value);"); } } else if (JAVA_LANG_INTEGER.equals(targetType) && JAVA_LANG_LONG.equals(sourceType)) { // Convert Long to Integer with bounds check and null handling @@ -166,13 +166,13 @@ public static StringBuilder generateNumericConversionCode( if (isBuilderMethod) { code.append(" return ").append(builderMethodName).append("((").append(JAVA_LANG_INTEGER).append(") null);\n"); } else { - code.append(" this.").append(fieldName).append(" = null;\n"); + code.append(" ").append(methodName).append("((").append(JAVA_LANG_INTEGER).append(") null);\n"); } code.append(" } else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {\n"); if (isBuilderMethod) { code.append(" return ").append(builderMethodName).append("(value.intValue());\n"); } else { - code.append(" this.").append(fieldName).append(" = value.intValue();\n"); + code.append(" ").append(methodName).append("(value.intValue());\n"); } code.append(" } else {\n"); code.append(" throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to Integer\");\n"); @@ -183,23 +183,23 @@ public static StringBuilder generateNumericConversionCode( if (isBuilderMethod) { code.append(" return ").append(builderMethodName).append("((").append(JAVA_LANG_LONG).append(") null);\n"); } else { - code.append(" this.").append(fieldName).append(" = null;\n"); + code.append(" ").append(methodName).append("((").append(JAVA_LANG_LONG).append(") null);\n"); } code.append(" } else {\n"); if (isBuilderMethod) { code.append(" return ").append(builderMethodName).append("(value.longValue());\n"); } else { - code.append(" this.").append(fieldName).append(" = value.longValue();\n"); + code.append(" ").append(methodName).append("(value.longValue());\n"); } code.append(" }"); } - + return code; } - + /** * Determines the appropriate overloaded method signature for a given field type. - * + * * @param fieldType The type of the field (e.g., "int", "java.lang.Integer") * @param methodName The setter method name * @return The signature of the overloaded method, or null if no overloaded method is needed @@ -217,7 +217,7 @@ public static String determineOverloadSignature(String fieldType, String methodN } return overloadSignature; } - + /** * Generates the complete code for an overloaded setter method with proper type conversion. * @@ -231,12 +231,12 @@ public static String determineOverloadSignature(String fieldType, String methodN public static StringBuilder generateOverloadedSetter( String methodName, String fieldName, String fieldType, boolean isBuilderMethod, String builderReturnType) { - + StringBuilder overloadedSetter = new StringBuilder(); String returnType = isBuilderMethod ? builderReturnType : "void"; String sourceType; String targetType = fieldType; - + // Determine the appropriate source type based on the target field type if ("int".equals(fieldType)) { sourceType = "long"; @@ -249,19 +249,19 @@ public static StringBuilder generateOverloadedSetter( } else { return overloadedSetter; // Empty if not a numeric type we handle } - + // Generate the method Javadoc overloadedSetter.append(generateNumericConversionJavadoc(fieldName, sourceType, targetType, isBuilderMethod)); - + // Generate the method signature overloadedSetter.append(generateNumericMethodSignature(methodName, sourceType, "value", returnType)); - - // Generate the conversion code - overloadedSetter.append(generateNumericConversionCode(fieldName, sourceType, targetType, isBuilderMethod, methodName)); - + + // Generate the conversion code - pass methodName as both the setter method name and builder method name + overloadedSetter.append(generateNumericConversionCode(sourceType, targetType, isBuilderMethod, methodName, methodName)); + // Close the method overloadedSetter.append("\n }"); - + return overloadedSetter; } } diff --git a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java index 55739fdde..f1c8d1647 100644 --- a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java +++ b/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformations.java @@ -1061,7 +1061,7 @@ public static String enhanceNumericPutMethod(String code) { /** * Adds overloaded setter methods for numeric fields to allow setting Integer fields with Long values * and Long fields with Integer values. This improves type compatibility when working with numeric fields. - * This method preserves existing setter methods and only adds the overloaded versions. + * This method identifies existing setter methods and adds appropriate overloaded versions in a single pass. * * @param code generated code * @return transformed code with overloaded numeric setter methods @@ -1071,78 +1071,57 @@ public static String addOverloadedNumericSetterMethods(String code) { return code; } - // First, identify all numeric fields in the class (both primitive and boxed) - Map fieldTypes = new HashMap<>(); // fieldName -> type - - // Pattern to match primitive field declarations - both public and private - Pattern primitiveFieldPattern = Pattern.compile("(public|private)\\s+(int|long)\\s+(\\w+)\\s*;"); - Matcher primitiveFieldMatcher = primitiveFieldPattern.matcher(code); - - while (primitiveFieldMatcher.find()) { - String fieldType = primitiveFieldMatcher.group(2); - String fieldName = primitiveFieldMatcher.group(3); - fieldTypes.put(fieldName, fieldType); - } - - // Pattern to match boxed field declarations - Pattern boxedFieldPattern = Pattern.compile("(public|private)\\s+(java\\.lang\\.Integer|java\\.lang\\.Long)\\s+(\\w+)\\s*;"); - Matcher boxedFieldMatcher = boxedFieldPattern.matcher(code); - - while (boxedFieldMatcher.find()) { - String fieldType = boxedFieldMatcher.group(2); - String fieldName = boxedFieldMatcher.group(3); - fieldTypes.put(fieldName, fieldType); - } - - if (fieldTypes.isEmpty()) { - return code; // No numeric fields found - } - StringBuilder result = new StringBuilder(code); - - // For each field, find its setter method and add an overloaded version - for (Map.Entry entry : fieldTypes.entrySet()) { - String fieldName = entry.getKey(); - String fieldType = entry.getValue(); - - // Convert field name to method name (camelCase to PascalCase) - String methodName = "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); - - // Find the position of the existing setter method - String setterSignature = "public void " + methodName + "(" + fieldType + " "; - int setterStart = code.indexOf(setterSignature); - - if (setterStart == -1) { - continue; // Setter not found - } - - // Find the end of the setter method using the helper method - int setterBodyStart = code.indexOf("{", setterStart); - if (setterBodyStart == -1) continue; + + // Unified pattern to match all numeric setter methods in a single pass + Pattern numericSetterPattern = Pattern.compile( + "public\\s+void\\s+(set\\w+)\\s*\\(\\s*(int|long|java\\.lang\\.Integer|java\\.lang\\.Long)\\s+([\\w$]+)\\s*\\)\\s*\\{"); + Matcher numericSetterMatcher = numericSetterPattern.matcher(code); + + // Track positions to insert overloaded methods + // Use a map of position -> insertion text to collect all insertions + Map insertions = new HashMap<>(); + + // Process all numeric setters in a single pass + while (numericSetterMatcher.find()) { + String methodName = numericSetterMatcher.group(1); + String paramType = numericSetterMatcher.group(2); - int setterEnd = CodeTransformationUtils.findEndOfBlock(code, setterBodyStart); - if (setterEnd == -1) continue; // Couldn't find proper end of method - - // Generate the overloaded setter using the utility method - StringBuilder overloadedSetter = CodeTransformationUtils.generateOverloadedSetter( - methodName, fieldName, fieldType, false, null); - - if (overloadedSetter.length() == 0) { - continue; // Not a numeric type we handle - } - - // Check if the overloaded method already exists in the code - String overloadSignature = CodeTransformationUtils.determineOverloadSignature(fieldType, methodName); - - // Only add if it doesn't already exist - if (overloadSignature != null && !code.contains(overloadSignature)) { - // Insert the overloaded setter after the existing setter - result.insert(setterEnd, overloadedSetter); - // Update the code string for subsequent searches - code = result.toString(); + // Derive the field name from the method name instead of parameter name + // Convert "setFieldName" to "fieldName" (remove "set" prefix and lowercase first char) + String fieldName = methodName.substring(3, 4).toLowerCase() + methodName.substring(4); + + // Find the end of the setter method + int methodBodyStart = code.indexOf("{", numericSetterMatcher.start()); + if (methodBodyStart == -1) continue; + + int methodEnd = CodeTransformationUtils.findEndOfBlock(code, methodBodyStart); + if (methodEnd == -1) continue; + + // Determine if we need to generate an overload based on the parameter type + String overloadSignature = CodeTransformationUtils.determineOverloadSignature(paramType, methodName); + + // Skip if no overload is needed or if this overload already exists + if (overloadSignature == null || code.contains(overloadSignature)) { + continue; } + + // Generate the complete overloaded setter method + StringBuilder overload = CodeTransformationUtils.generateOverloadedSetter( + methodName, fieldName, paramType, false, null); + + // Schedule the insertion + insertions.put(methodEnd, overload.toString()); } - + + // Apply all insertions in reverse order to maintain correct positions + List positions = new ArrayList<>(insertions.keySet()); + Collections.sort(positions, Collections.reverseOrder()); + + for (int position : positions) { + result.insert(position, insertions.get(position)); + } + return result.toString(); } @@ -1375,7 +1354,7 @@ private static void parseConstructorParameters(String constructorParams, } pos++; - } + } // Process the last parameter if (startPos < len) { @@ -1479,7 +1458,7 @@ public static String addOverloadedNumericBuilderSetterMethods(String code) { while (builderSetterMatcher.find()) { String methodName = builderSetterMatcher.group(1); String paramType = builderSetterMatcher.group(2); - String paramName = builderSetterMatcher.group(3); + String fieldName = builderSetterMatcher.group(3); // Only process methods that start with "set" if (!methodName.startsWith("set")) { @@ -1499,7 +1478,7 @@ public static String addOverloadedNumericBuilderSetterMethods(String code) { // Generate the overloaded setter using the utility method StringBuilder overloadedSetter = CodeTransformationUtils.generateOverloadedSetter( - methodName, paramName, paramType, true, builderReturnType); + methodName, fieldName, paramType, true, builderReturnType); if (overloadedSetter.length() == 0) { continue; // Not a numeric type we handle diff --git a/helper/helper-common/src/test/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtilsTest.java b/helper/helper-common/src/test/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtilsTest.java index a0eaab355..de9fffa13 100644 --- a/helper/helper-common/src/test/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtilsTest.java +++ b/helper/helper-common/src/test/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtilsTest.java @@ -11,7 +11,6 @@ public class CodeTransformationUtilsTest { - @Test public void testFindEndOfBlock() { // Test basic code block @@ -74,58 +73,57 @@ public void testGenerateNumericConversionJavadoc() { public void testGenerateNumericMethodSignature() { // Test regular setter signature StringBuilder sig1 = CodeTransformationUtils.generateNumericMethodSignature( - "setValue", "long", "value", "void"); - Assert.assertEquals(sig1.toString(), " public void setValue(long value) {\n"); + "setValueField", "long", "value", "void"); + Assert.assertEquals(sig1.toString(), " public void setValueField(long value) {\n"); // Test builder method signature StringBuilder sig2 = CodeTransformationUtils.generateNumericMethodSignature( - "withCount", "java.lang.Integer", "value", "Builder"); - Assert.assertEquals(sig2.toString(), " public Builder withCount(java.lang.Integer value) {\n"); + "setValueField", "java.lang.Integer", "value", "Builder"); + Assert.assertEquals(sig2.toString(), " public Builder setValueField(java.lang.Integer value) {\n"); } @Test public void testGenerateNumericConversionCode() { // Test long to int conversion (regular setter) - StringBuilder code1 = CodeTransformationUtils.generateNumericConversionCode( - "count", "long", "int", false, null); + StringBuilder code1 = CodeTransformationUtils.generateNumericConversionCode("long", "int", false, "setSizeField", + null); Assert.assertTrue(code1.toString().contains("if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE)")); - Assert.assertTrue(code1.toString().contains("this.count = (int) value")); + Assert.assertTrue(code1.toString().contains("setSizeField((int) value)")); Assert.assertTrue(code1.toString().contains("throw new org.apache.avro.AvroRuntimeException")); // Test int to long conversion (builder method) - StringBuilder code2 = CodeTransformationUtils.generateNumericConversionCode( - "size", "int", "long", true, "withSize"); - Assert.assertTrue(code2.toString().contains("return withSize((long) value)")); + StringBuilder code2 = CodeTransformationUtils.generateNumericConversionCode("int", "long", true, null, "setSizeField"); + Assert.assertTrue(code2.toString().contains("return setSizeField((long) value)")); // Test Long to Integer conversion (regular setter with null handling) - StringBuilder code3 = CodeTransformationUtils.generateNumericConversionCode( - "count", "java.lang.Long", "java.lang.Integer", false, null); + StringBuilder code3 = CodeTransformationUtils.generateNumericConversionCode("java.lang.Long", "java.lang.Integer", false, + "setSizeField", null); Assert.assertTrue(code3.toString().contains("if (value == null)")); - Assert.assertTrue(code3.toString().contains("this.count = null")); - Assert.assertTrue(code3.toString().contains("this.count = value.intValue()")); + Assert.assertTrue(code3.toString().contains("setSizeField((java.lang.Integer) null)")); + Assert.assertTrue(code3.toString().contains("setSizeField(value.intValue())")); // Test Integer to Long conversion (builder method with null handling) - StringBuilder code4 = CodeTransformationUtils.generateNumericConversionCode( - "size", "java.lang.Integer", "java.lang.Long", true, "withSize"); + StringBuilder code4 = CodeTransformationUtils.generateNumericConversionCode("java.lang.Integer", "java.lang.Long", true, + null, "setSizeField"); Assert.assertTrue(code4.toString().contains("if (value == null)")); - Assert.assertTrue(code4.toString().contains("return withSize((java.lang.Long) null)")); - Assert.assertTrue(code4.toString().contains("return withSize(value.longValue())")); + Assert.assertTrue(code4.toString().contains("return setSizeField((java.lang.Long) null)")); + Assert.assertTrue(code4.toString().contains("return setSizeField(value.longValue())")); } @Test public void testDetermineOverloadSignature() { // Test primitive types - Assert.assertEquals(CodeTransformationUtils.determineOverloadSignature("int", "setValue"), + Assert.assertEquals(CodeTransformationUtils.determineOverloadSignature("int", "setValue"), "public void setValue(long "); - Assert.assertEquals(CodeTransformationUtils.determineOverloadSignature("long", "setValue"), + Assert.assertEquals(CodeTransformationUtils.determineOverloadSignature("long", "setValue"), "public void setValue(int "); - + // Test wrapper types - Assert.assertEquals(CodeTransformationUtils.determineOverloadSignature("java.lang.Integer", "setValue"), + Assert.assertEquals(CodeTransformationUtils.determineOverloadSignature("java.lang.Integer", "setValue"), "public void setValue(java.lang.Long "); - Assert.assertEquals(CodeTransformationUtils.determineOverloadSignature("java.lang.Long", "setValue"), + Assert.assertEquals(CodeTransformationUtils.determineOverloadSignature("java.lang.Long", "setValue"), "public void setValue(java.lang.Integer "); - + // Test unsupported type Assert.assertNull(CodeTransformationUtils.determineOverloadSignature("String", "setValue")); } @@ -137,22 +135,22 @@ public void testGenerateOverloadedSetter() { "setCount", "count", "int", false, null); Assert.assertTrue(setter1.toString().contains("public void setCount(long value)")); Assert.assertTrue(setter1.toString().contains("if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE)")); - Assert.assertTrue(setter1.toString().contains("this.count = (int) value")); - + Assert.assertTrue(setter1.toString().contains("setCount((int) value)")); + // Test primitive long field with builder method StringBuilder setter2 = CodeTransformationUtils.generateOverloadedSetter( "withSize", "size", "long", true, "Builder"); Assert.assertTrue(setter2.toString().contains("public Builder withSize(int value)")); Assert.assertTrue(setter2.toString().contains("return withSize((long) value)")); - + // Test wrapper Integer field with regular setter StringBuilder setter3 = CodeTransformationUtils.generateOverloadedSetter( "setCount", "count", "java.lang.Integer", false, null); Assert.assertTrue(setter3.toString().contains("public void setCount(java.lang.Long value)")); Assert.assertTrue(setter3.toString().contains("if (value == null)")); - Assert.assertTrue(setter3.toString().contains("this.count = null")); - Assert.assertTrue(setter3.toString().contains("this.count = value.intValue()")); - + Assert.assertTrue(setter3.toString().contains("setCount((java.lang.Integer) null)")); + Assert.assertTrue(setter3.toString().contains("setCount(value.intValue())")); + // Test wrapper Long field with builder method StringBuilder setter4 = CodeTransformationUtils.generateOverloadedSetter( "withSize", "size", "java.lang.Long", true, "Builder"); @@ -160,7 +158,7 @@ public void testGenerateOverloadedSetter() { Assert.assertTrue(setter4.toString().contains("if (value == null)")); Assert.assertTrue(setter4.toString().contains("return withSize((java.lang.Long) null)")); Assert.assertTrue(setter4.toString().contains("return withSize(value.longValue())")); - + // Test unsupported type (should return empty StringBuilder) StringBuilder setter5 = CodeTransformationUtils.generateOverloadedSetter( "setName", "name", "String", false, null); diff --git a/helper/tests/helper-tests-14/src/test/java/com/linkedin/avroutil1/compatibility/avro14/CodeTransformationsAvro14Test.java b/helper/tests/helper-tests-14/src/test/java/com/linkedin/avroutil1/compatibility/avro14/CodeTransformationsAvro14Test.java index f9b733dda..e0aa7d1f5 100644 --- a/helper/tests/helper-tests-14/src/test/java/com/linkedin/avroutil1/compatibility/avro14/CodeTransformationsAvro14Test.java +++ b/helper/tests/helper-tests-14/src/test/java/com/linkedin/avroutil1/compatibility/avro14/CodeTransformationsAvro14Test.java @@ -170,64 +170,6 @@ public void testEnhanceNumericPutMethod() throws Exception { } } - @Test - public void testAddOverloadedNumericSetterMethods() throws Exception { - // Use the IntsAndLongs schema for testing - String avsc = TestUtil.load("IntsAndLongs.avsc"); - Schema schema = AvroCompatibilityHelper.parse(avsc); - String originalCode = runNativeCodegen(schema); - - // Apply the transformation - String enhancedCode = CodeTransformations.addOverloadedNumericSetterMethods(originalCode); - - // Verify the enhanced code contains the overloaded setters - Assert.assertTrue(enhancedCode.contains("public void setIntField(long value)")); - Assert.assertTrue(enhancedCode.contains("public void setLongField(int value)")); - Assert.assertTrue(enhancedCode.contains("public void setBoxedIntField(java.lang.Long value)")); - Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); - - // Compile the enhanced code to verify it's valid Java - Class generatedClass = null; - try { - generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); - } catch (Exception e) { - Assert.fail("Enhanced setter methods code should compile without errors"); - } - - Assert.assertNotNull(generatedClass.getMethod("setIntField", long.class)); - Assert.assertNotNull(generatedClass.getMethod("setLongField", int.class)); - Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); - Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); - } - - @Test - public void testAddOverloadedNumericConstructor() throws Exception { - // Use the IntsAndLongs schema for testing - String avsc = TestUtil.load("IntsAndLongs.avsc"); - Schema schema = AvroCompatibilityHelper.parse(avsc); - String originalCode = runNativeCodegen(schema); - - // Apply the transformation - String enhancedCode = CodeTransformations.addOverloadedNumericConstructor(originalCode); - - // Verify the enhanced code contains the overloaded constructor with swapped types - Assert.assertTrue(enhancedCode.contains("public " + schema.getName() + "(java.lang.Integer longField, java.lang.Long " - + "intField, java.lang.Integer boxedLongField, java.lang.Long boxedIntField)")); - - // Compile the enhanced code to verify it's valid Java - Class generatedClass = null; - try { - generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); - } catch (Exception e) { - Assert.fail("Enhanced constructor code should compile without errors"); - } - - Assert.assertNotNull(generatedClass.getConstructor( - java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class)); - Assert.assertNotNull(generatedClass.getConstructor( - java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class)); - } - private String runNativeCodegen(Schema schema) throws Exception { File outputRoot = Files.createTempDirectory(null).toFile(); return runNativeCodegen(schema, outputRoot); diff --git a/helper/tests/helper-tests-15/src/test/java/com/linkedin/avroutil1/compatibility/avro15/CodeTransformationsAvro15Test.java b/helper/tests/helper-tests-15/src/test/java/com/linkedin/avroutil1/compatibility/avro15/CodeTransformationsAvro15Test.java index 748ecf7f5..bf3e13b74 100644 --- a/helper/tests/helper-tests-15/src/test/java/com/linkedin/avroutil1/compatibility/avro15/CodeTransformationsAvro15Test.java +++ b/helper/tests/helper-tests-15/src/test/java/com/linkedin/avroutil1/compatibility/avro15/CodeTransformationsAvro15Test.java @@ -86,64 +86,6 @@ public void testEnhanceNumericPutMethod() throws Exception { } } - @Test - public void testAddOverloadedNumericSetterMethods() throws Exception { - // Use the IntsAndLongs schema for testing - String avsc = TestUtil.load("IntsAndLongs.avsc"); - Schema schema = AvroCompatibilityHelper.parse(avsc); - String originalCode = runNativeCodegen(schema); - - // Apply the transformation - String enhancedCode = CodeTransformations.addOverloadedNumericSetterMethods(originalCode); - - // Verify the enhanced code contains the overloaded setters - Assert.assertTrue(enhancedCode.contains("public void setIntField(long value)")); - Assert.assertTrue(enhancedCode.contains("public void setLongField(int value)")); - Assert.assertTrue(enhancedCode.contains("public void setBoxedIntField(java.lang.Long value)")); - Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); - - // Compile the enhanced code to verify it's valid Java - Class generatedClass = null; - try { - generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); - } catch (Exception e) { - Assert.fail("Enhanced setter methods code should compile without errors"); - } - - Assert.assertNotNull(generatedClass.getMethod("setIntField", long.class)); - Assert.assertNotNull(generatedClass.getMethod("setLongField", int.class)); - Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); - Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); - } - - @Test - public void testAddOverloadedNumericConstructor() throws Exception { - // Use the IntsAndLongs schema for testing - String avsc = TestUtil.load("IntsAndLongs.avsc"); - Schema schema = AvroCompatibilityHelper.parse(avsc); - String originalCode = runNativeCodegen(schema); - - // Apply the transformation - String enhancedCode = CodeTransformations.addOverloadedNumericConstructor(originalCode); - - // Verify the enhanced code contains the overloaded constructor with swapped types - Assert.assertTrue(enhancedCode.contains("public " + schema.getName() + "(java.lang.Integer longField, java.lang.Long " - + "intField, java.lang.Integer boxedLongField, java.lang.Long boxedIntField)")); - - // Compile the enhanced code to verify it's valid Java - Class generatedClass = null; - try { - generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); - } catch (Exception e) { - Assert.fail("Enhanced constructor code should compile without errors"); - } - - Assert.assertNotNull(generatedClass.getConstructor( - java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class)); - Assert.assertNotNull(generatedClass.getConstructor( - java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class)); - } - private String runNativeCodegen(Schema schema) throws Exception { File outputRoot = Files.createTempDirectory(null).toFile(); SpecificCompiler compiler = new SpecificCompiler(schema); From 8fa530ba2e9c21c6ae2009a4c6fd15762da00da7 Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Mon, 16 Jun 2025 17:49:46 -0700 Subject: [PATCH 14/15] updated tests --- .../avro16/CodeTransformationsAvro16Test.java | 36 +++---------------- .../avro17/CodeTransformationsAvro17Test.java | 8 ++--- .../avro18/CodeTransformationsAvro18Test.java | 10 +++--- 3 files changed, 13 insertions(+), 41 deletions(-) diff --git a/helper/tests/helper-tests-16/src/test/java/com/linkedin/avroutil1/compatibility/avro16/CodeTransformationsAvro16Test.java b/helper/tests/helper-tests-16/src/test/java/com/linkedin/avroutil1/compatibility/avro16/CodeTransformationsAvro16Test.java index 484651bc1..fa6871ce0 100644 --- a/helper/tests/helper-tests-16/src/test/java/com/linkedin/avroutil1/compatibility/avro16/CodeTransformationsAvro16Test.java +++ b/helper/tests/helper-tests-16/src/test/java/com/linkedin/avroutil1/compatibility/avro16/CodeTransformationsAvro16Test.java @@ -84,8 +84,8 @@ public void testAddOverloadedNumericSetterMethods() throws Exception { String enhancedCode = CodeTransformations.addOverloadedNumericSetterMethods(originalCode); // Verify the enhanced code contains the overloaded setters - Assert.assertTrue(enhancedCode.contains("public void setIntField(long value)")); - Assert.assertTrue(enhancedCode.contains("public void setLongField(int value)")); + Assert.assertTrue(enhancedCode.contains("public void setIntField(java.lang.Long value)")); + Assert.assertTrue(enhancedCode.contains("public void setLongField(java.lang.Integer value)")); Assert.assertTrue(enhancedCode.contains("public void setBoxedIntField(java.lang.Long value)")); Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); @@ -97,40 +97,12 @@ public void testAddOverloadedNumericSetterMethods() throws Exception { Assert.fail("Enhanced setter methods code should compile without errors"); } - Assert.assertNotNull(generatedClass.getMethod("setIntField", long.class)); - Assert.assertNotNull(generatedClass.getMethod("setLongField", int.class)); + Assert.assertNotNull(generatedClass.getMethod("setIntField", java.lang.Long.class)); + Assert.assertNotNull(generatedClass.getMethod("setLongField", java.lang.Integer.class)); Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); } - @Test - public void testAddOverloadedNumericConstructor() throws Exception { - // Use the IntsAndLongs schema for testing - String avsc = TestUtil.load("IntsAndLongs.avsc"); - Schema schema = AvroCompatibilityHelper.parse(avsc); - String originalCode = runNativeCodegen(schema); - - // Apply the transformation - String enhancedCode = CodeTransformations.addOverloadedNumericConstructor(originalCode); - - // Verify the enhanced code contains the overloaded constructor with swapped types - Assert.assertTrue(enhancedCode.contains("public " + schema.getName() + "(java.lang.Integer longField, java.lang.Long " - + "intField, java.lang.Integer boxedLongField, java.lang.Long boxedIntField)")); - - // Compile the enhanced code to verify it's valid Java - Class generatedClass = null; - try { - generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); - } catch (Exception e) { - Assert.fail("Enhanced constructor code should compile without errors"); - } - - Assert.assertNotNull(generatedClass.getConstructor( - java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class)); - Assert.assertNotNull(generatedClass.getConstructor( - java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class)); - } - private String runNativeCodegen(Schema schema) throws Exception { File outputRoot = Files.createTempDirectory(null).toFile(); SpecificCompiler compiler = new SpecificCompiler(schema); diff --git a/helper/tests/helper-tests-17/src/test/java/com/linkedin/avroutil1/compatibility/avro17/CodeTransformationsAvro17Test.java b/helper/tests/helper-tests-17/src/test/java/com/linkedin/avroutil1/compatibility/avro17/CodeTransformationsAvro17Test.java index eb305e28c..417e4a055 100644 --- a/helper/tests/helper-tests-17/src/test/java/com/linkedin/avroutil1/compatibility/avro17/CodeTransformationsAvro17Test.java +++ b/helper/tests/helper-tests-17/src/test/java/com/linkedin/avroutil1/compatibility/avro17/CodeTransformationsAvro17Test.java @@ -84,8 +84,8 @@ public void testAddOverloadedNumericSetterMethods() throws Exception { String enhancedCode = CodeTransformations.addOverloadedNumericSetterMethods(originalCode); // Verify the enhanced code contains the overloaded setters - Assert.assertTrue(enhancedCode.contains("public void setIntField(long value)")); - Assert.assertTrue(enhancedCode.contains("public void setLongField(int value)")); + Assert.assertTrue(enhancedCode.contains("public void setIntField(java.lang.Long value)")); + Assert.assertTrue(enhancedCode.contains("public void setLongField(java.lang.Integer value)")); Assert.assertTrue(enhancedCode.contains("public void setBoxedIntField(java.lang.Long value)")); Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); @@ -97,8 +97,8 @@ public void testAddOverloadedNumericSetterMethods() throws Exception { Assert.fail("Enhanced setter methods code should compile without errors"); } - Assert.assertNotNull(generatedClass.getMethod("setIntField", long.class)); - Assert.assertNotNull(generatedClass.getMethod("setLongField", int.class)); + Assert.assertNotNull(generatedClass.getMethod("setIntField", java.lang.Long.class)); + Assert.assertNotNull(generatedClass.getMethod("setLongField", java.lang.Integer.class)); Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); } diff --git a/helper/tests/helper-tests-18/src/test/java/com/linkedin/avroutil1/compatibility/avro18/CodeTransformationsAvro18Test.java b/helper/tests/helper-tests-18/src/test/java/com/linkedin/avroutil1/compatibility/avro18/CodeTransformationsAvro18Test.java index 54fd61fe1..083e5c0fa 100644 --- a/helper/tests/helper-tests-18/src/test/java/com/linkedin/avroutil1/compatibility/avro18/CodeTransformationsAvro18Test.java +++ b/helper/tests/helper-tests-18/src/test/java/com/linkedin/avroutil1/compatibility/avro18/CodeTransformationsAvro18Test.java @@ -91,8 +91,8 @@ public void testAddOverloadedNumericSetterMethods() throws Exception { String enhancedCode = CodeTransformations.addOverloadedNumericSetterMethods(originalCode); // Verify the enhanced code contains the overloaded setters - Assert.assertTrue(enhancedCode.contains("public void setIntField(long value)")); - Assert.assertTrue(enhancedCode.contains("public void setLongField(int value)")); + Assert.assertTrue(enhancedCode.contains("public void setIntField(java.lang.Long value)")); + Assert.assertTrue(enhancedCode.contains("public void setLongField(java.lang.Integer value)")); Assert.assertTrue(enhancedCode.contains("public void setBoxedIntField(java.lang.Long value)")); Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); @@ -104,11 +104,11 @@ public void testAddOverloadedNumericSetterMethods() throws Exception { Assert.fail("Enhanced setter methods code should compile without errors"); } - Assert.assertNotNull(generatedClass.getMethod("setIntField", long.class)); - Assert.assertNotNull(generatedClass.getMethod("setLongField", int.class)); + Assert.assertNotNull(generatedClass.getMethod("setIntField", java.lang.Long.class)); + Assert.assertNotNull(generatedClass.getMethod("setLongField", java.lang.Integer.class)); Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); - } + } @Test public void testAddOverloadedNumericConstructor() throws Exception { From 70bfab318578392734486c8498fe8e733706bf23 Mon Sep 17 00:00:00 2001 From: Emma Nguyen Date: Mon, 16 Jun 2025 21:01:50 -0700 Subject: [PATCH 15/15] Tests --- .../CodeTransformationsAvro110Test.java | 24 ++--- .../CodeTransformationsAvro111Test.java | 87 +++++++++++++++++-- .../avro16/CodeTransformationsAvro16Test.java | 11 +-- .../avro17/CodeTransformationsAvro17Test.java | 11 +-- .../avro18/CodeTransformationsAvro18Test.java | 14 +-- .../avro19/CodeTransformationsAvro19Test.java | 12 +-- 6 files changed, 96 insertions(+), 63 deletions(-) diff --git a/helper/tests/helper-tests-110/src/test/java/com/linkedin/avroutil1/compatibility/avro110/CodeTransformationsAvro110Test.java b/helper/tests/helper-tests-110/src/test/java/com/linkedin/avroutil1/compatibility/avro110/CodeTransformationsAvro110Test.java index 53cc6b1bb..d3eabc8f9 100644 --- a/helper/tests/helper-tests-110/src/test/java/com/linkedin/avroutil1/compatibility/avro110/CodeTransformationsAvro110Test.java +++ b/helper/tests/helper-tests-110/src/test/java/com/linkedin/avroutil1/compatibility/avro110/CodeTransformationsAvro110Test.java @@ -6,10 +6,16 @@ package com.linkedin.avroutil1.compatibility.avro110; -import com.linkedin.avroutil1.testcommon.TestUtil; import com.linkedin.avroutil1.compatibility.AvroCompatibilityHelper; import com.linkedin.avroutil1.compatibility.AvroVersion; import com.linkedin.avroutil1.compatibility.CodeTransformations; +import com.linkedin.avroutil1.testcommon.TestUtil; +import java.io.File; +import java.io.FileInputStream; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.regex.Matcher; import net.openhft.compiler.CompilerUtils; import org.apache.avro.Schema; import org.apache.avro.compiler.specific.SpecificCompiler; @@ -17,14 +23,6 @@ import org.apache.commons.io.IOUtils; import org.testng.Assert; import org.testng.annotations.Test; - -import java.io.File; -import java.io.FileInputStream; -import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.regex.Matcher; - import under110wbuildersmin18.NormalRecordWithoutReferences; @@ -197,17 +195,11 @@ public void testAddOverloadedNumericSetterMethods() throws Exception { Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); // Compile the enhanced code to verify it's valid Java - Class generatedClass = null; try { - generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); } catch (Exception e) { Assert.fail("Enhanced setter methods code should compile without errors"); } - - Assert.assertNotNull(generatedClass.getMethod("setIntField", long.class)); - Assert.assertNotNull(generatedClass.getMethod("setLongField", int.class)); - Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); - Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); } @Test diff --git a/helper/tests/helper-tests-111/src/test/java/com/linkedin/avroutil1/compatibility/avro111/CodeTransformationsAvro111Test.java b/helper/tests/helper-tests-111/src/test/java/com/linkedin/avroutil1/compatibility/avro111/CodeTransformationsAvro111Test.java index e03f194f8..fa0ec610b 100644 --- a/helper/tests/helper-tests-111/src/test/java/com/linkedin/avroutil1/compatibility/avro111/CodeTransformationsAvro111Test.java +++ b/helper/tests/helper-tests-111/src/test/java/com/linkedin/avroutil1/compatibility/avro111/CodeTransformationsAvro111Test.java @@ -10,6 +10,12 @@ import com.linkedin.avroutil1.compatibility.AvroVersion; import com.linkedin.avroutil1.compatibility.CodeTransformations; import com.linkedin.avroutil1.testcommon.TestUtil; +import java.io.File; +import java.io.FileInputStream; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.regex.Matcher; import net.openhft.compiler.CompilerUtils; import org.apache.avro.Schema; import org.apache.avro.compiler.specific.SpecificCompiler; @@ -17,14 +23,6 @@ import org.apache.commons.io.IOUtils; import org.testng.Assert; import org.testng.annotations.Test; - -import java.io.File; -import java.io.FileInputStream; -import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.regex.Matcher; - import under111wbuildersmin18.NormalRecordWithoutReferences; @@ -84,6 +82,79 @@ public void testStripCustomCoders() throws Exception { public void testBuilders() throws Exception { Assert.assertNotNull(NormalRecordWithoutReferences.newBuilder()); } + @Test + public void testEnhanceNumericPutMethod() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.enhanceNumericPutMethod(originalCode); + + // Verify the enhanced code contains the type checking for numeric conversions + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Long)")); + Assert.assertTrue(enhancedCode.contains("if (value$ instanceof java.lang.Integer)")); + + // Compile the enhanced code to verify it's valid Java + try { + CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced put method code should compile without errors"); + } + } + + @Test + public void testAddOverloadedNumericSetterMethods() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericSetterMethods(originalCode); + + // Verify the enhanced code contains the overloaded setters + Assert.assertTrue(enhancedCode.contains("public void setIntField(long value)")); + Assert.assertTrue(enhancedCode.contains("public void setLongField(int value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedIntField(java.lang.Long value)")); + Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); + + // Compile the enhanced code to verify it's valid Java + try { + CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced setter methods code should compile without errors"); + } + } + + @Test + public void testAddOverloadedNumericConstructor() throws Exception { + // Use the IntsAndLongs schema for testing + String avsc = TestUtil.load("IntsAndLongs.avsc"); + Schema schema = AvroCompatibilityHelper.parse(avsc); + String originalCode = runNativeCodegen(schema); + + // Apply the transformation + String enhancedCode = CodeTransformations.addOverloadedNumericConstructor(originalCode); + + // Verify the enhanced code contains the overloaded constructor with swapped types + Assert.assertTrue(enhancedCode.contains("public " + schema.getName() + "(java.lang.Integer longField, java.lang.Long " + + "intField, java.lang.Integer boxedLongField, java.lang.Long boxedIntField)")); + + // Compile the enhanced code to verify it's valid Java + Class generatedClass = null; + try { + generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + } catch (Exception e) { + Assert.fail("Enhanced constructor code should compile without errors"); + } + + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class)); + Assert.assertNotNull(generatedClass.getConstructor( + java.lang.Integer.class, java.lang.Long.class, java.lang.Integer.class, java.lang.Long.class)); + } private String runNativeCodegen(Schema schema) throws Exception { File outputRoot = Files.createTempDirectory(null).toFile(); diff --git a/helper/tests/helper-tests-16/src/test/java/com/linkedin/avroutil1/compatibility/avro16/CodeTransformationsAvro16Test.java b/helper/tests/helper-tests-16/src/test/java/com/linkedin/avroutil1/compatibility/avro16/CodeTransformationsAvro16Test.java index fa6871ce0..609529685 100644 --- a/helper/tests/helper-tests-16/src/test/java/com/linkedin/avroutil1/compatibility/avro16/CodeTransformationsAvro16Test.java +++ b/helper/tests/helper-tests-16/src/test/java/com/linkedin/avroutil1/compatibility/avro16/CodeTransformationsAvro16Test.java @@ -6,16 +6,15 @@ package com.linkedin.avroutil1.compatibility.avro16; -import com.linkedin.avroutil1.testcommon.TestUtil; import com.linkedin.avroutil1.compatibility.AvroCompatibilityHelper; import com.linkedin.avroutil1.compatibility.AvroVersion; import com.linkedin.avroutil1.compatibility.CodeTransformations; +import com.linkedin.avroutil1.testcommon.TestUtil; import java.io.File; import java.io.FileInputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.regex.Matcher; - import net.openhft.compiler.CompilerUtils; import org.apache.avro.Schema; import org.apache.avro.compiler.specific.SpecificCompiler; @@ -90,17 +89,11 @@ public void testAddOverloadedNumericSetterMethods() throws Exception { Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); // Compile the enhanced code to verify it's valid Java - Class generatedClass = null; try { - generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); } catch (Exception e) { Assert.fail("Enhanced setter methods code should compile without errors"); } - - Assert.assertNotNull(generatedClass.getMethod("setIntField", java.lang.Long.class)); - Assert.assertNotNull(generatedClass.getMethod("setLongField", java.lang.Integer.class)); - Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); - Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); } private String runNativeCodegen(Schema schema) throws Exception { diff --git a/helper/tests/helper-tests-17/src/test/java/com/linkedin/avroutil1/compatibility/avro17/CodeTransformationsAvro17Test.java b/helper/tests/helper-tests-17/src/test/java/com/linkedin/avroutil1/compatibility/avro17/CodeTransformationsAvro17Test.java index 417e4a055..7c91a14f4 100644 --- a/helper/tests/helper-tests-17/src/test/java/com/linkedin/avroutil1/compatibility/avro17/CodeTransformationsAvro17Test.java +++ b/helper/tests/helper-tests-17/src/test/java/com/linkedin/avroutil1/compatibility/avro17/CodeTransformationsAvro17Test.java @@ -6,16 +6,15 @@ package com.linkedin.avroutil1.compatibility.avro17; -import com.linkedin.avroutil1.testcommon.TestUtil; import com.linkedin.avroutil1.compatibility.AvroCompatibilityHelper; import com.linkedin.avroutil1.compatibility.AvroVersion; import com.linkedin.avroutil1.compatibility.CodeTransformations; +import com.linkedin.avroutil1.testcommon.TestUtil; import java.io.File; import java.io.FileInputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.regex.Matcher; - import net.openhft.compiler.CompilerUtils; import org.apache.avro.Schema; import org.apache.avro.compiler.specific.SpecificCompiler; @@ -90,17 +89,11 @@ public void testAddOverloadedNumericSetterMethods() throws Exception { Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); // Compile the enhanced code to verify it's valid Java - Class generatedClass = null; try { - generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); } catch (Exception e) { Assert.fail("Enhanced setter methods code should compile without errors"); } - - Assert.assertNotNull(generatedClass.getMethod("setIntField", java.lang.Long.class)); - Assert.assertNotNull(generatedClass.getMethod("setLongField", java.lang.Integer.class)); - Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); - Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); } @Test diff --git a/helper/tests/helper-tests-18/src/test/java/com/linkedin/avroutil1/compatibility/avro18/CodeTransformationsAvro18Test.java b/helper/tests/helper-tests-18/src/test/java/com/linkedin/avroutil1/compatibility/avro18/CodeTransformationsAvro18Test.java index 083e5c0fa..fd897cacd 100644 --- a/helper/tests/helper-tests-18/src/test/java/com/linkedin/avroutil1/compatibility/avro18/CodeTransformationsAvro18Test.java +++ b/helper/tests/helper-tests-18/src/test/java/com/linkedin/avroutil1/compatibility/avro18/CodeTransformationsAvro18Test.java @@ -6,23 +6,21 @@ package com.linkedin.avroutil1.compatibility.avro18; -import com.linkedin.avroutil1.testcommon.TestUtil; import com.linkedin.avroutil1.compatibility.AvroCompatibilityHelper; import com.linkedin.avroutil1.compatibility.AvroVersion; import com.linkedin.avroutil1.compatibility.CodeTransformations; +import com.linkedin.avroutil1.testcommon.TestUtil; import java.io.File; import java.io.FileInputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.regex.Matcher; - import net.openhft.compiler.CompilerUtils; import org.apache.avro.Schema; import org.apache.avro.compiler.specific.SpecificCompiler; import org.apache.commons.io.IOUtils; import org.testng.Assert; import org.testng.annotations.Test; - import under18wbuildersmin18.NormalRecordWithoutReferences; @@ -97,18 +95,12 @@ public void testAddOverloadedNumericSetterMethods() throws Exception { Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); // Compile the enhanced code to verify it's valid Java - Class generatedClass = null; try { - generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); } catch (Exception e) { Assert.fail("Enhanced setter methods code should compile without errors"); } - - Assert.assertNotNull(generatedClass.getMethod("setIntField", java.lang.Long.class)); - Assert.assertNotNull(generatedClass.getMethod("setLongField", java.lang.Integer.class)); - Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); - Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); - } + } @Test public void testAddOverloadedNumericConstructor() throws Exception { diff --git a/helper/tests/helper-tests-19/src/test/java/com/linkedin/avroutil1/compatibility/avro19/CodeTransformationsAvro19Test.java b/helper/tests/helper-tests-19/src/test/java/com/linkedin/avroutil1/compatibility/avro19/CodeTransformationsAvro19Test.java index 09e55dfab..bf4c730ed 100644 --- a/helper/tests/helper-tests-19/src/test/java/com/linkedin/avroutil1/compatibility/avro19/CodeTransformationsAvro19Test.java +++ b/helper/tests/helper-tests-19/src/test/java/com/linkedin/avroutil1/compatibility/avro19/CodeTransformationsAvro19Test.java @@ -6,17 +6,16 @@ package com.linkedin.avroutil1.compatibility.avro19; -import com.linkedin.avroutil1.testcommon.TestUtil; import com.linkedin.avroutil1.compatibility.AvroCompatibilityHelper; import com.linkedin.avroutil1.compatibility.AvroVersion; import com.linkedin.avroutil1.compatibility.CodeTransformations; +import com.linkedin.avroutil1.testcommon.TestUtil; import java.io.File; import java.io.FileInputStream; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.regex.Matcher; - import net.openhft.compiler.CompilerUtils; import org.apache.avro.Schema; import org.apache.avro.compiler.specific.SpecificCompiler; @@ -24,7 +23,6 @@ import org.apache.commons.io.IOUtils; import org.testng.Assert; import org.testng.annotations.Test; - import under19wbuildersmin18.NormalRecordWithoutReferences; @@ -185,17 +183,11 @@ public void testAddOverloadedNumericSetterMethods() throws Exception { Assert.assertTrue(enhancedCode.contains("public void setBoxedLongField(java.lang.Integer value)")); // Compile the enhanced code to verify it's valid Java - Class generatedClass = null; try { - generatedClass = CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); + CompilerUtils.CACHED_COMPILER.loadFromJava(schema.getFullName(), enhancedCode); } catch (Exception e) { Assert.fail("Enhanced setter methods code should compile without errors"); } - - Assert.assertNotNull(generatedClass.getMethod("setIntField", long.class)); - Assert.assertNotNull(generatedClass.getMethod("setLongField", int.class)); - Assert.assertNotNull(generatedClass.getMethod("setBoxedIntField", java.lang.Long.class)); - Assert.assertNotNull(generatedClass.getMethod("setBoxedLongField", java.lang.Integer.class)); } @Test