From ae5840ea8218816ea67c489fa49b0dbc85ffdbf6 Mon Sep 17 00:00:00 2001 From: Victoria Rozhina Date: Mon, 22 Sep 2025 11:43:28 -0700 Subject: [PATCH 1/3] Revert "Fix SpecificRecordClassGenerator methods overloaded for int/long (#587)" This reverts commit 1bc561aa2253602e1172f0cfebe5d5ec50997311. --- .../src/main/avro/vs110/IntsAndLongs.avsc | 5 - .../src/main/avro/vs111/IntsAndLongs.avsc | 5 - .../src/main/avro/vs14/IntsAndLongs.avsc | 5 - .../src/main/avro/vs15/IntsAndLongs.avsc | 5 - .../src/main/avro/vs16/IntsAndLongs.avsc | 5 - .../src/main/avro/vs17/IntsAndLongs.avsc | 5 - .../src/main/avro/vs18/IntsAndLongs.avsc | 5 - .../src/main/avro/vs19/IntsAndLongs.avsc | 5 - .../avro/noUtf8Encoding/TestCollections.avsc | 4 - .../avroutil1/builder/SpecificRecordTest.java | 8 - .../codegen/SpecificRecordClassGenerator.java | 163 +++++++----------- .../SpecificRecordClassGeneratorTest.java | 3 +- .../test/resources/schemas/IntLongString.avsc | 64 ------- 13 files changed, 67 insertions(+), 215 deletions(-) delete mode 100644 avro-codegen/src/test/resources/schemas/IntLongString.avsc diff --git a/avro-builder/tests/codegen-110/src/main/avro/vs110/IntsAndLongs.avsc b/avro-builder/tests/codegen-110/src/main/avro/vs110/IntsAndLongs.avsc index a67809331..bc4541bad 100644 --- a/avro-builder/tests/codegen-110/src/main/avro/vs110/IntsAndLongs.avsc +++ b/avro-builder/tests/codegen-110/src/main/avro/vs110/IntsAndLongs.avsc @@ -20,11 +20,6 @@ "name": "boxedIntField", "type": ["null", "int"], "default": null - }, - { - "name": "strField", - "type": "string", - "default": "defaultStr" } ] } \ No newline at end of file diff --git a/avro-builder/tests/codegen-111/src/main/avro/vs111/IntsAndLongs.avsc b/avro-builder/tests/codegen-111/src/main/avro/vs111/IntsAndLongs.avsc index b8a3e16bc..94d6b271a 100644 --- a/avro-builder/tests/codegen-111/src/main/avro/vs111/IntsAndLongs.avsc +++ b/avro-builder/tests/codegen-111/src/main/avro/vs111/IntsAndLongs.avsc @@ -20,11 +20,6 @@ "name": "boxedIntField", "type": ["null", "int"], "default": null - }, - { - "name": "strField", - "type": "string", - "default": "defaultStr" } ] } \ No newline at end of file diff --git a/avro-builder/tests/codegen-14/src/main/avro/vs14/IntsAndLongs.avsc b/avro-builder/tests/codegen-14/src/main/avro/vs14/IntsAndLongs.avsc index 8f033f9ca..94a330a5d 100644 --- a/avro-builder/tests/codegen-14/src/main/avro/vs14/IntsAndLongs.avsc +++ b/avro-builder/tests/codegen-14/src/main/avro/vs14/IntsAndLongs.avsc @@ -20,11 +20,6 @@ "name": "boxedIntField", "type": ["null", "int"], "default": null - }, - { - "name": "strField", - "type": "string", - "default": "defaultStr" } ] } \ No newline at end of file diff --git a/avro-builder/tests/codegen-15/src/main/avro/vs15/IntsAndLongs.avsc b/avro-builder/tests/codegen-15/src/main/avro/vs15/IntsAndLongs.avsc index 8ef879853..ececfcb68 100644 --- a/avro-builder/tests/codegen-15/src/main/avro/vs15/IntsAndLongs.avsc +++ b/avro-builder/tests/codegen-15/src/main/avro/vs15/IntsAndLongs.avsc @@ -20,11 +20,6 @@ "name": "boxedIntField", "type": ["null", "int"], "default": null - }, - { - "name": "strField", - "type": "string", - "default": "defaultStr" } ] } \ No newline at end of file diff --git a/avro-builder/tests/codegen-16/src/main/avro/vs16/IntsAndLongs.avsc b/avro-builder/tests/codegen-16/src/main/avro/vs16/IntsAndLongs.avsc index 0806b8fc4..5243ce289 100644 --- a/avro-builder/tests/codegen-16/src/main/avro/vs16/IntsAndLongs.avsc +++ b/avro-builder/tests/codegen-16/src/main/avro/vs16/IntsAndLongs.avsc @@ -20,11 +20,6 @@ "name": "boxedIntField", "type": ["null", "int"], "default": null - }, - { - "name": "strField", - "type": "string", - "default": "defaultStr" } ] } \ No newline at end of file diff --git a/avro-builder/tests/codegen-17/src/main/avro/vs17/IntsAndLongs.avsc b/avro-builder/tests/codegen-17/src/main/avro/vs17/IntsAndLongs.avsc index ebe63e541..93248975a 100644 --- a/avro-builder/tests/codegen-17/src/main/avro/vs17/IntsAndLongs.avsc +++ b/avro-builder/tests/codegen-17/src/main/avro/vs17/IntsAndLongs.avsc @@ -20,11 +20,6 @@ "name": "boxedIntField", "type": ["null", "int"], "default": null - }, - { - "name": "strField", - "type": "string", - "default": "defaultStr" } ] } \ No newline at end of file diff --git a/avro-builder/tests/codegen-18/src/main/avro/vs18/IntsAndLongs.avsc b/avro-builder/tests/codegen-18/src/main/avro/vs18/IntsAndLongs.avsc index d66156488..143afa554 100644 --- a/avro-builder/tests/codegen-18/src/main/avro/vs18/IntsAndLongs.avsc +++ b/avro-builder/tests/codegen-18/src/main/avro/vs18/IntsAndLongs.avsc @@ -20,11 +20,6 @@ "name": "boxedIntField", "type": ["null", "int"], "default": null - }, - { - "name": "strField", - "type": "string", - "default": "defaultStr" } ] } \ No newline at end of file diff --git a/avro-builder/tests/codegen-19/src/main/avro/vs19/IntsAndLongs.avsc b/avro-builder/tests/codegen-19/src/main/avro/vs19/IntsAndLongs.avsc index f30f17872..535c58b02 100644 --- a/avro-builder/tests/codegen-19/src/main/avro/vs19/IntsAndLongs.avsc +++ b/avro-builder/tests/codegen-19/src/main/avro/vs19/IntsAndLongs.avsc @@ -20,11 +20,6 @@ "name": "boxedIntField", "type": ["null", "int"], "default": null - }, - { - "name": "strField", - "type": "string", - "default": "defaultStr" } ] } \ No newline at end of file diff --git a/avro-builder/tests/codegen-no-utf8-encoding/src/main/avro/noUtf8Encoding/TestCollections.avsc b/avro-builder/tests/codegen-no-utf8-encoding/src/main/avro/noUtf8Encoding/TestCollections.avsc index 37dab82df..576d1314d 100644 --- a/avro-builder/tests/codegen-no-utf8-encoding/src/main/avro/noUtf8Encoding/TestCollections.avsc +++ b/avro-builder/tests/codegen-no-utf8-encoding/src/main/avro/noUtf8Encoding/TestCollections.avsc @@ -89,10 +89,6 @@ "values": "int" } ] - }, - { - "name": "intField", - "type": "int" } ], "type": "record" diff --git a/avro-builder/tests/tests-allavro/src/test/java/com/linkedin/avroutil1/builder/SpecificRecordTest.java b/avro-builder/tests/tests-allavro/src/test/java/com/linkedin/avroutil1/builder/SpecificRecordTest.java index 990c5cb18..ba559044f 100644 --- a/avro-builder/tests/tests-allavro/src/test/java/com/linkedin/avroutil1/builder/SpecificRecordTest.java +++ b/avro-builder/tests/tests-allavro/src/test/java/com/linkedin/avroutil1/builder/SpecificRecordTest.java @@ -1999,21 +1999,18 @@ public void testIntLongRecords(Class clazz) throws Exception { Method setIntFieldLongMethod = builder.getClass().getMethod("setIntField", long.class); Method setLongFieldMethod = builder.getClass().getMethod("setLongField", long.class); Method setLongFieldIntMethod = builder.getClass().getMethod("setLongField", int.class); - Method setStrFieldIntMethod = builder.getClass().getMethod("setStrField", String.class); Method buildMethod = builder.getClass().getMethod("build"); // Case 1: Set int and long with matching int/long types (primitive) Object builder1 = newBuilderMethod.invoke(null); setIntFieldMethod.invoke(builder1, 123); setLongFieldMethod.invoke(builder1, 456L); - setStrFieldIntMethod.invoke(builder1, "testStr"); Object record1 = buildMethod.invoke(builder1); // Case 2: Set int field with long type, and long field with int type (primitive) Object builder2 = newBuilderMethod.invoke(null); setIntFieldLongMethod.invoke(builder2, 123L); setLongFieldIntMethod.invoke(builder2, 456); - setStrFieldIntMethod.invoke(builder2, "testStr"); Object record2 = buildMethod.invoke(builder2); // Case 3: Using the put method with primitive types @@ -2021,19 +2018,16 @@ public void testIntLongRecords(Class clazz) throws Exception { Method putMethod = clazz.getMethod("put", int.class, Object.class); putMethod.invoke(record3, 0, 456L); // longField with long putMethod.invoke(record3, 1, 123); // intField with int - putMethod.invoke(record3, 4, "testStr"); // Case 4: Using the put method with cross types Object record4 = clazz.newInstance(); putMethod.invoke(record4, 0, 456); // longField with int putMethod.invoke(record4, 1, 123L); // intField with long - putMethod.invoke(record4, 4, "testStr"); // Case 5: Using the put method with Integer/Long wrapper classes Object record5 = clazz.newInstance(); putMethod.invoke(record5, 0, Integer.valueOf(456)); // longField with Integer putMethod.invoke(record5, 1, Long.valueOf(123L)); // intField with Long - putMethod.invoke(record5, 4, "testStr"); // Verify all records are equal Assert.assertEquals(record1, record2); @@ -2044,13 +2038,11 @@ public void testIntLongRecords(Class clazz) throws Exception { // Verify field values directly Method getIntFieldMethod = clazz.getMethod("getIntField"); Method getLongFieldMethod = clazz.getMethod("getLongField"); - Method getStrFieldMethod = clazz.getMethod("getStrField"); Assert.assertEquals(123, getIntFieldMethod.invoke(record1)); Assert.assertEquals(456L, getLongFieldMethod.invoke(record1)); Assert.assertEquals(123, getIntFieldMethod.invoke(record5)); Assert.assertEquals(456L, getLongFieldMethod.invoke(record5)); - Assert.assertEquals("testStr", getStrFieldMethod.invoke(record1)); } @Test(dataProvider = "intsAndLongsDataProvider") diff --git a/avro-codegen/src/main/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGenerator.java b/avro-codegen/src/main/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGenerator.java index 1b9dcf6fd..deeba0f64 100644 --- a/avro-codegen/src/main/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGenerator.java +++ b/avro-codegen/src/main/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGenerator.java @@ -553,95 +553,67 @@ private void addAllArgsConstructor(AvroRecordSchema recordSchema, if(recordSchema.getFields().size() < 254) { MethodSpec.Builder allArgsConstructorBuilder = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC); for (AvroSchemaField field : recordSchema.getFields()) { - handleConstructorField(allArgsConstructorBuilder, field, defaultMethodStringRepresentation, disableStringTransform); - } - annotateDeprecatedConstructor(allArgsConstructorBuilder, defaultMethodStringRepresentation, recordSchema); - classBuilder.addMethod(allArgsConstructorBuilder.build()); - } - } - - /** - * Handles a field in a constructor. - * - * @param classBuilder The builder for the class being generated. - * @param field The AvroSchemaField to handle. - * @param defaultMethodStringRepresentation The default method string representation to use. - * @param disableStringTransform If true, disables string transformation for fields. - */ - private void handleConstructorField(MethodSpec.Builder classBuilder, AvroSchemaField field, - AvroJavaStringRepresentation defaultMethodStringRepresentation, boolean disableStringTransform) { - - //if declared schema, use fully qualified class (no import) - String escapedFieldName = getFieldNameWithSuffix(field); - classBuilder.addParameter(getParameterSpecForField(field, defaultMethodStringRepresentation)); - if(!disableStringTransform && SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.STRING, field.getSchema())) { - classBuilder.addStatement( - "this.$1L = com.linkedin.avroutil1.compatibility.StringConverterUtil.getUtf8($1L)", - escapedFieldName); - } else if (SpecificRecordGeneratorUtil.isListTransformerApplicableForSchema(field.getSchema(), disableStringTransform)) { - classBuilder.addStatement( - "this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.convertToUtf8($1L)", - escapedFieldName); - } else if (SpecificRecordGeneratorUtil.isMapTransformerApplicable(field.getSchema(), disableStringTransform)) { - classBuilder.addStatement( - "this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.convertToUtf8($1L)", - escapedFieldName); - } else if (field.getSchema() != null && AvroType.UNION.equals(field.getSchema().type()) - && !SpecificRecordGeneratorUtil.isSingleTypeNullableUnionSchema(field.getSchema())) { + //if declared schema, use fully qualified class (no import) + String escapedFieldName = getFieldNameWithSuffix(field); + allArgsConstructorBuilder.addParameter(getParameterSpecForField(field, defaultMethodStringRepresentation)); + if(!disableStringTransform && SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.STRING, field.getSchema())) { + allArgsConstructorBuilder.addStatement( + "this.$1L = com.linkedin.avroutil1.compatibility.StringConverterUtil.getUtf8($1L)", + escapedFieldName); + } else if (SpecificRecordGeneratorUtil.isListTransformerApplicableForSchema(field.getSchema(), disableStringTransform)) { + allArgsConstructorBuilder.addStatement( + "this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.convertToUtf8($1L)", + escapedFieldName); + } else if (SpecificRecordGeneratorUtil.isMapTransformerApplicable(field.getSchema(), disableStringTransform)) { + allArgsConstructorBuilder.addStatement( + "this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.convertToUtf8($1L)", + escapedFieldName); + } else if (field.getSchema() != null && AvroType.UNION.equals(field.getSchema().type()) + && !SpecificRecordGeneratorUtil.isSingleTypeNullableUnionSchema(field.getSchema())) { + + allArgsConstructorBuilder.beginControlFlow("if ($1L == null)", escapedFieldName) + .addStatement("this.$1L = null", escapedFieldName) + .endControlFlow(); - classBuilder.beginControlFlow("if ($1L == null)", escapedFieldName) - .addStatement("this.$1L = null", escapedFieldName) - .endControlFlow(); + // if union might contain string value in runtime + for (SchemaOrRef unionMemberSchema : ((AvroUnionSchema) field.getSchema()).getTypes()) { + if (!disableStringTransform && SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.STRING, unionMemberSchema.getSchema())) { + allArgsConstructorBuilder.beginControlFlow("else if($1L instanceof $2T)", escapedFieldName, + CharSequence.class) + .addStatement("this.$1L = com.linkedin.avroutil1.compatibility.StringConverterUtil.getUtf8($1L)", + escapedFieldName) + .endControlFlow(); + } else if (SpecificRecordGeneratorUtil.isListTransformerApplicableForSchema( + unionMemberSchema.getSchema(), disableStringTransform)) { + allArgsConstructorBuilder.beginControlFlow("else if($1L instanceof $2T)", escapedFieldName, List.class) + .addStatement( + "this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.convertToUtf8($1L)", + escapedFieldName) + .endControlFlow(); + } else if (SpecificRecordGeneratorUtil.isMapTransformerApplicable(unionMemberSchema.getSchema(), disableStringTransform)) { + allArgsConstructorBuilder.beginControlFlow("else if($1L instanceof $2T)", escapedFieldName, Map.class) + .addStatement( + "this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.convertToUtf8($1L)", + escapedFieldName) + .endControlFlow(); + } + } - // if union might contain string value in runtime - for (SchemaOrRef unionMemberSchema : ((AvroUnionSchema) field.getSchema()).getTypes()) { - if (!disableStringTransform && SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.STRING, unionMemberSchema.getSchema())) { - classBuilder.beginControlFlow("else if($1L instanceof $2T)", escapedFieldName, - CharSequence.class) - .addStatement("this.$1L = com.linkedin.avroutil1.compatibility.StringConverterUtil.getUtf8($1L)", - escapedFieldName) - .endControlFlow(); - } else if (SpecificRecordGeneratorUtil.isListTransformerApplicableForSchema( - unionMemberSchema.getSchema(), disableStringTransform)) { - classBuilder.beginControlFlow("else if($1L instanceof $2T)", escapedFieldName, List.class) - .addStatement( - "this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.ListTransformer.convertToUtf8($1L)", - escapedFieldName) - .endControlFlow(); - } else if (SpecificRecordGeneratorUtil.isMapTransformerApplicable(unionMemberSchema.getSchema(), disableStringTransform)) { - classBuilder.beginControlFlow("else if($1L instanceof $2T)", escapedFieldName, Map.class) - .addStatement( - "this.$1L = com.linkedin.avroutil1.compatibility.collectiontransformer.MapTransformer.convertToUtf8($1L)", - escapedFieldName) + allArgsConstructorBuilder.beginControlFlow("else") + .addStatement("this.$1L = $1L", escapedFieldName) .endControlFlow(); + } else { + allArgsConstructorBuilder.addStatement("this.$1L = $1L", escapedFieldName); } } - classBuilder.beginControlFlow("else") - .addStatement("this.$1L = $1L", escapedFieldName) - .endControlFlow(); - } else { - classBuilder.addStatement("this.$1L = $1L", escapedFieldName); - } - } + //CharSequence constructors are deprecated in favor of String constructors + if (defaultMethodStringRepresentation.equals(AvroJavaStringRepresentation.CHAR_SEQUENCE) + && SpecificRecordGeneratorUtil.recordHasSimpleStringField(recordSchema)) { + allArgsConstructorBuilder.addAnnotation(Deprecated.class); + } - /** - * Annotates the constructor as deprecated if the schema has string fields and the default method string - * representation is CharSequence. CharSequence constructors are deprecated in favor of String constructors. - * - * @param classBuilder The builder for the class being generated. - * @param defaultMethodStringRepresentation The default method string representation. - * @param recordSchema The Avro record schema. - */ - private void annotateDeprecatedConstructor( - MethodSpec.Builder classBuilder, - AvroJavaStringRepresentation defaultMethodStringRepresentation, - AvroRecordSchema recordSchema - ) { - if (defaultMethodStringRepresentation.equals(AvroJavaStringRepresentation.CHAR_SEQUENCE) - && SpecificRecordGeneratorUtil.recordHasSimpleStringField(recordSchema) - ) { - classBuilder.addAnnotation(Deprecated.class); + classBuilder.addMethod(allArgsConstructorBuilder.build()); } } @@ -691,7 +663,6 @@ private boolean hasIntOrLongField(AvroRecordSchema recordSchema) { */ private void addMixedNumericConversionConstructor(AvroRecordSchema recordSchema, SpecificRecordGenerationConfig config, TypeSpec.Builder classBuilder) { - AvroJavaStringRepresentation defaultMethodStringRepresentation = config.getDefaultMethodStringRepresentation(); MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC); @@ -758,11 +729,12 @@ private void addMixedNumericConversionConstructor(AvroRecordSchema recordSchema, } } else { // For other fields, use their normal type - handleConstructorField(constructorBuilder, field, - defaultMethodStringRepresentation, !config.isUtf8EncodingEnabled()); + constructorBuilder.addParameter( + getParameterSpecForField(field, AvroJavaStringRepresentation.STRING)); + constructorBuilder.addStatement("this.$1L = $1L", escapedFieldName); + } } - annotateDeprecatedConstructor(constructorBuilder, defaultMethodStringRepresentation, recordSchema); classBuilder.addMethod(constructorBuilder.build()); } @@ -1099,13 +1071,13 @@ private MethodSpec.Builder createNumericTypeOverloadedSetterBuilder(Class fie if (fieldClass != null) { methodBuilder = MethodSpec .methodBuilder(getMethodNameForFieldWithPrefix("set", escapedFieldName)) - .addModifiers(Modifier.PUBLIC) - .addStatement("validate(fields()[$L], value)", fieldIndex); + .addModifiers(Modifier.PUBLIC); if (fieldClass.equals(long.class)) { // If field is of type long, add method with int parameter methodBuilder .addParameter(TypeName.INT, "value") + .addStatement("validate(fields()[$L], value)", fieldIndex) .addStatement("this.$L = value", escapedFieldName); } else if (fieldClass.equals(int.class)) { @@ -1125,14 +1097,10 @@ private MethodSpec.Builder createNumericTypeOverloadedSetterBuilder(Class fie field.getSchemaOrRef().getSchema().type(), true, config.getDefaultMethodStringRepresentation()); - methodBuilder = MethodSpec - .methodBuilder(getMethodNameForFieldWithPrefix("set", escapedFieldName)) - .addModifiers(Modifier.PUBLIC) - .addStatement("validate(fields()[$L], value)", fieldIndex);; - if (typeName.equals(ClassName.get(Long.class))) { // For long or Long fields, add a setter that accepts int or Integer - methodBuilder + methodBuilder = MethodSpec + .methodBuilder(getMethodNameForFieldWithPrefix("set", escapedFieldName)) .addParameter(ClassName.get(Integer.class), "value") .beginControlFlow("if (value == null)") .addStatement("this.$L = null", escapedFieldName) @@ -1142,7 +1110,8 @@ private MethodSpec.Builder createNumericTypeOverloadedSetterBuilder(Class fie } else if (typeName.equals(ClassName.get(Integer.class))) { - methodBuilder + methodBuilder = MethodSpec + .methodBuilder(getMethodNameForFieldWithPrefix("set", escapedFieldName)) .addParameter(ClassName.get(Long.class), "value") .beginControlFlow("if (value == null)") .addStatement("this.$L = null", escapedFieldName) @@ -1302,7 +1271,7 @@ private String getSerializedCustomDecodeBlock(SpecificRecordGenerationConfig con codeBlockBuilder .beginControlFlow("try") .addStatement("$L = in.readInt()", fieldName) - .nextControlFlow("catch ($T e)", Exception.class) + .nextControlFlow("catch (Exception e)") .addStatement("// If int decoding fails, try long decoding with bounds check") .addStatement("long $L = in.readLong()", tempIntVarName) .beginControlFlow("if ($L <= Integer.MAX_VALUE && $L >= Integer.MIN_VALUE)", tempIntVarName, tempIntVarName) @@ -1326,7 +1295,7 @@ private String getSerializedCustomDecodeBlock(SpecificRecordGenerationConfig con "temp" + Character.toUpperCase(cleanLongFieldName.charAt(0)) + cleanLongFieldName.substring(1); codeBlockBuilder.beginControlFlow("try") .addStatement("$L = in.readLong()", fieldName) - .nextControlFlow("catch ($T e)", Exception.class) + .nextControlFlow("catch (Exception e)") .addStatement("// If long decoding fails, try int decoding with conversion to long") .addStatement("int $L = in.readInt()", tempLongVarName) .addStatement("$L = (long) $L", fieldName, tempLongVarName) diff --git a/avro-codegen/src/test/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGeneratorTest.java b/avro-codegen/src/test/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGeneratorTest.java index 97e4c0aba..ad70905a3 100644 --- a/avro-codegen/src/test/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGeneratorTest.java +++ b/avro-codegen/src/test/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGeneratorTest.java @@ -38,8 +38,7 @@ Object[][] schemaPaths() { {"schemas/SimpleFixedWithHugeDoc.avsc", true}, {"schemas/TestCollections.avsc", true}, {"schemas/TestProblematicRecord.avsc", true}, - {"schemas/TestRecord.avsc", false}, - {"schemas/IntLongString.avsc", true} + {"schemas/TestRecord.avsc", false} }; } diff --git a/avro-codegen/src/test/resources/schemas/IntLongString.avsc b/avro-codegen/src/test/resources/schemas/IntLongString.avsc deleted file mode 100644 index 4cdb9a064..000000000 --- a/avro-codegen/src/test/resources/schemas/IntLongString.avsc +++ /dev/null @@ -1,64 +0,0 @@ -{ - "type": "record", - "namespace": "com.linkedin.test", - "name": "IntLongString", - "fields": [ - { - "name": "longField", - "type": "long" - }, - { - "name": "intField", - "type": "int" - }, - { - "name": "boxedLongField", - "type": ["null", "long"], - "default": null - }, - { - "name": "boxedIntField", - "type": ["null", "int"], - "default": null - }, - { - "name": "str", - "type": "string" - }, - { - "name": "strAr", - "type": { - "type": "array", - "items": "string" - } - }, - { - "name": "strArAr", - "type": { - "type": "array", - "items": { - "type": "array", - "items": "string" - } - } - }, - { - "name": "unionOfArray", - "type": ["null", { - "type": "array", - "items": "string" - } - ] - }, - { - "name": "arOfMap", - "type": { - "type": "array", - "items": { - "type": "map", - "values": "string" - } - } - } - ] -} \ No newline at end of file From 5e5ff635258f545f42e9cfb1ea4032ccc7f04fdf Mon Sep 17 00:00:00 2001 From: Victoria Rozhina Date: Mon, 22 Sep 2025 11:43:45 -0700 Subject: [PATCH 2/3] Revert "Updating CodeTransformations to enable int/long setters/puts conversions (#583)" This reverts commit d44abd10fb05d7e87c51b100e8ab4a74d06a446b. --- .../CodeTransformationUtils.java | 267 -------- .../compatibility/CodeTransformations.java | 593 +----------------- .../CodeTransformationUtilsTest.java | 167 ----- .../IntsAndLongsWithBuilder.avsc | 25 - .../compat-avro/under110/IntsAndLongs.avsc | 25 - .../IntsAndLongsWithBuilder.avsc | 25 - .../compat-avro/under111/IntsAndLongs.avsc | 25 - .../compat-avro/under14/IntsAndLongs.avsc | 25 - .../compat-avro/under15/IntsAndLongs.avsc | 25 - .../compat-avro/under16/IntsAndLongs.avsc | 25 - .../compat-avro/under17/IntsAndLongs.avsc | 25 - .../IntsAndLongsWithBuilder.avsc | 25 - .../compat-avro/under18/IntsAndLongs.avsc | 25 - .../IntsAndLongsWithBuilder.avsc | 25 - .../compat-avro/under19/IntsAndLongs.avsc | 25 - .../CodeTransformationsAvro110Test.java | 90 +-- .../CodeTransformationsAvro111Test.java | 87 +-- .../avro14/CodeTransformationsAvro14Test.java | 22 - .../avro15/CodeTransformationsAvro15Test.java | 22 - .../avro16/CodeTransformationsAvro16Test.java | 49 +- .../avro17/CodeTransformationsAvro17Test.java | 77 +-- .../avro18/CodeTransformationsAvro18Test.java | 78 +-- .../avro19/CodeTransformationsAvro19Test.java | 78 +-- .../src/main/resources/IntsAndLongs.avsc | 25 - 24 files changed, 29 insertions(+), 1826 deletions(-) delete mode 100644 helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtils.java delete mode 100644 helper/helper-common/src/test/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtilsTest.java delete mode 100644 helper/tests/codegen-110/src/main/compat-avro-w-builders/under110wbuilders/IntsAndLongsWithBuilder.avsc delete mode 100644 helper/tests/codegen-110/src/main/compat-avro/under110/IntsAndLongs.avsc delete mode 100644 helper/tests/codegen-111/src/main/compat-avro-w-builders/under111wbuilders/IntsAndLongsWithBuilder.avsc delete mode 100644 helper/tests/codegen-111/src/main/compat-avro/under111/IntsAndLongs.avsc delete mode 100644 helper/tests/codegen-14/src/main/compat-avro/under14/IntsAndLongs.avsc delete mode 100644 helper/tests/codegen-15/src/main/compat-avro/under15/IntsAndLongs.avsc delete mode 100644 helper/tests/codegen-16/src/main/compat-avro/under16/IntsAndLongs.avsc delete mode 100644 helper/tests/codegen-17/src/main/compat-avro/under17/IntsAndLongs.avsc delete mode 100644 helper/tests/codegen-18/src/main/compat-avro-w-builders/under18wbuilders/IntsAndLongsWithBuilder.avsc delete mode 100644 helper/tests/codegen-18/src/main/compat-avro/under18/IntsAndLongs.avsc delete mode 100644 helper/tests/codegen-19/src/main/compat-avro-w-builders/under19wbuilders/IntsAndLongsWithBuilder.avsc delete mode 100644 helper/tests/codegen-19/src/main/compat-avro/under19/IntsAndLongs.avsc delete mode 100644 helper/tests/helper-tests-common/src/main/resources/IntsAndLongs.avsc 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 deleted file mode 100644 index 3362511d8..000000000 --- a/helper/helper-common/src/main/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtils.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright 2025 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 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 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(" ").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"); - 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(" ").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 - code.append(" if (value == null) {\n"); - if (isBuilderMethod) { - code.append(" return ").append(builderMethodName).append("((").append(JAVA_LANG_INTEGER).append(") null);\n"); - } else { - 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(" ").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"); - 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(" ").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(" ").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 - */ - 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 - 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 f1c8d1647..2e2f04593 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,6 +6,8 @@ package com.linkedin.avroutil1.compatibility; +import org.apache.commons.text.StringEscapeUtils; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -17,7 +19,6 @@ import java.util.StringJoiner; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.commons.text.StringEscapeUtils; /** @@ -99,12 +100,6 @@ 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 int MAX_STRING_LITERAL_SIZE = 65000; //just under 64k private CodeTransformations() { @@ -136,11 +131,6 @@ 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.addOverloadedNumericSetterMethods(fixed); - fixed = CodeTransformations.addOverloadedNumericConstructor(fixed); - //1.6+ features if (minSupportedVersion.earlierThan(AvroVersion.AVRO_1_6)) { //optionally strip out builders @@ -151,7 +141,6 @@ 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); @@ -930,584 +919,6 @@ 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. - * 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 - */ - public static String enhanceNumericPutMethod(String code) { - if (code == null || code.isEmpty()) { - return code; - } - - // Pattern to match the put method with simple casts - Matcher matcher = PUT_METHOD_PATTERN.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 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); - - 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}); - } - } - - if (fieldInfo.isEmpty()) { - return code; // No numeric fields found - } - - // 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"); - - // 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 (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"); - 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 (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"); - enhancedPutMethod.append(" this.").append(fieldName).append(" = (java.lang.Long)value$;\n"); - enhancedPutMethod.append(" }\n"); - enhancedPutMethod.append(" break;\n"); - } - } else { - // 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"); - } - - enhancedPutMethod.append(" }\n}"); - - // Replace the original put method with the enhanced one - return code.substring(0, matcher.start()) + enhancedPutMethod + code.substring(matcher.end()); - } - - /** - * 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 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 - */ - public static String addOverloadedNumericSetterMethods(String code) { - if (code == null || code.isEmpty()) { - return code; - } - - StringBuilder result = new StringBuilder(code); - - // 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); - - // 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(); - } - - /** - * 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); - - // Try to find a constructor with parameters - 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(); - 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(); - } - } - } - - // 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); - - // Swap Java types for numeric fields - 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); - } - } - - 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) && 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"); - 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) && 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 (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"); - 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 (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"); - 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(constructorEnd, "\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); - } - } - - - /** - * 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 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 - } - - 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 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 fieldName = builderSetterMatcher.group(3); - - // Only process methods that start with "set" - if (!methodName.startsWith("set")) { - continue; - } - - // Find the position of the method body start - int methodStart = builderSetterMatcher.start() + offset; - int methodBodyStart = modifiedBuilderClass.indexOf("{", methodStart); - 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, fieldName, 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 - String overloadSignature = null; - if ("int".equals(paramType)) { - overloadSignature = "public " + builderReturnType + " " + methodName + "(long "; - } else if ("long".equals(paramType)) { - overloadSignature = "public " + builderReturnType + " " + methodName + "(int "; - } else if (CodeTransformationUtils.JAVA_LANG_INTEGER.equals(paramType)) { - overloadSignature = "public " + builderReturnType + " " + methodName + "(java.lang.Long "; - } else if (CodeTransformationUtils.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; 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 deleted file mode 100644 index de9fffa13..000000000 --- a/helper/helper-common/src/test/java/com/linkedin/avroutil1/compatibility/CodeTransformationUtilsTest.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * 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( - "setValueField", "long", "value", "void"); - Assert.assertEquals(sig1.toString(), " public void setValueField(long value) {\n"); - - // Test builder method signature - StringBuilder sig2 = CodeTransformationUtils.generateNumericMethodSignature( - "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("long", "int", false, "setSizeField", - null); - Assert.assertTrue(code1.toString().contains("if (value <= Integer.MAX_VALUE && value >= Integer.MIN_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("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("java.lang.Long", "java.lang.Integer", false, - "setSizeField", null); - Assert.assertTrue(code3.toString().contains("if (value == null)")); - 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("java.lang.Integer", "java.lang.Long", true, - null, "setSizeField"); - Assert.assertTrue(code4.toString().contains("if (value == null)")); - 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"), - "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("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("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"); - 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); - } -} 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 deleted file mode 100644 index fa0395191..000000000 --- a/helper/tests/codegen-110/src/main/compat-avro-w-builders/under110wbuilders/IntsAndLongsWithBuilder.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "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-110/src/main/compat-avro/under110/IntsAndLongs.avsc b/helper/tests/codegen-110/src/main/compat-avro/under110/IntsAndLongs.avsc deleted file mode 100644 index 50fa51cce..000000000 --- a/helper/tests/codegen-110/src/main/compat-avro/under110/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "under110", - "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 - } - ] -} \ No newline at end of file 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 deleted file mode 100644 index 29a7c3c5c..000000000 --- a/helper/tests/codegen-111/src/main/compat-avro-w-builders/under111wbuilders/IntsAndLongsWithBuilder.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "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-111/src/main/compat-avro/under111/IntsAndLongs.avsc b/helper/tests/codegen-111/src/main/compat-avro/under111/IntsAndLongs.avsc deleted file mode 100644 index 22ed16555..000000000 --- a/helper/tests/codegen-111/src/main/compat-avro/under111/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "under111", - "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 - } - ] -} 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 deleted file mode 100644 index da42ec88c..000000000 --- a/helper/tests/codegen-14/src/main/compat-avro/under14/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "under14", - "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 - } - ] -} 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 deleted file mode 100644 index 7101398f7..000000000 --- a/helper/tests/codegen-15/src/main/compat-avro/under15/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "under15", - "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 - } - ] -} 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 deleted file mode 100644 index 4c5243b68..000000000 --- a/helper/tests/codegen-16/src/main/compat-avro/under16/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "under16", - "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 - } - ] -} 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 deleted file mode 100644 index 3782dd70d..000000000 --- a/helper/tests/codegen-17/src/main/compat-avro/under17/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "under17", - "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 - } - ] -} 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 deleted file mode 100644 index 95e4b4738..000000000 --- a/helper/tests/codegen-18/src/main/compat-avro-w-builders/under18wbuilders/IntsAndLongsWithBuilder.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "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-18/src/main/compat-avro/under18/IntsAndLongs.avsc b/helper/tests/codegen-18/src/main/compat-avro/under18/IntsAndLongs.avsc deleted file mode 100644 index c4b301b1c..000000000 --- a/helper/tests/codegen-18/src/main/compat-avro/under18/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "under18", - "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 - } - ] -} 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 deleted file mode 100644 index 09fb0fbec..000000000 --- a/helper/tests/codegen-19/src/main/compat-avro-w-builders/under19wbuilders/IntsAndLongsWithBuilder.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "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 - } - ] -} 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 deleted file mode 100644 index 68b6be6ec..000000000 --- a/helper/tests/codegen-19/src/main/compat-avro/under19/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "under19", - "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 - } - ] -} 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 d3eabc8f9..24268670d 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,16 +6,10 @@ 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; @@ -23,6 +17,14 @@ 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; @@ -156,80 +158,6 @@ 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(); SpecificCompiler compiler = new SpecificCompiler(schema); 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 fa0ec610b..e03f194f8 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,12 +10,6 @@ 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; @@ -23,6 +17,14 @@ 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; @@ -82,79 +84,6 @@ 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-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 e0aa7d1f5..f0c6fd3c2 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,28 +148,6 @@ 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"); - } - } - 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 bf3e13b74..5f97a0e28 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,28 +64,6 @@ 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"); - } - } - 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 609529685..52251b002 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,15 +6,16 @@ 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; @@ -50,52 +51,6 @@ 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(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)")); - - // 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"); - } - } - 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 7c91a14f4..f44dc5cf5 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,15 +6,16 @@ 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; @@ -50,80 +51,6 @@ 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(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)")); - - // 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(); 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 fd897cacd..006a8cffd 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,21 +6,23 @@ 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; @@ -56,80 +58,6 @@ 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(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)")); - - // 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(); 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 bf4c730ed..79f98b38a 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,16 +6,17 @@ 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; @@ -23,6 +24,7 @@ import org.apache.commons.io.IOUtils; import org.testng.Assert; import org.testng.annotations.Test; + import under19wbuildersmin18.NormalRecordWithoutReferences; @@ -144,80 +146,6 @@ 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(); 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 deleted file mode 100644 index e80229e9f..000000000 --- a/helper/tests/helper-tests-common/src/main/resources/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "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 7b05ef18e73ac8f741e73cf0b74d8791e303b603 Mon Sep 17 00:00:00 2001 From: Victoria Rozhina Date: Mon, 22 Sep 2025 11:43:54 -0700 Subject: [PATCH 3/3] Revert "Updating SpecificRecordClassGenerator to enable int to long setters / builders (#581)" This reverts commit b0ad33a4e1822ef6a9633f665fadf12a6662da02. --- .../src/main/avro/vs110/IntsAndLongs.avsc | 25 - .../src/main/avro/vs111/IntsAndLongs.avsc | 25 - .../src/main/avro/vs14/IntsAndLongs.avsc | 25 - .../src/main/avro/vs15/IntsAndLongs.avsc | 25 - .../src/main/avro/vs16/IntsAndLongs.avsc | 25 - .../src/main/avro/vs17/IntsAndLongs.avsc | 25 - .../src/main/avro/vs18/IntsAndLongs.avsc | 25 - .../src/main/avro/vs19/IntsAndLongs.avsc | 25 - .../avroutil1/builder/SpecificRecordTest.java | 127 +----- .../codegen/SpecificRecordClassGenerator.java | 428 +----------------- 10 files changed, 17 insertions(+), 738 deletions(-) delete mode 100644 avro-builder/tests/codegen-110/src/main/avro/vs110/IntsAndLongs.avsc delete mode 100644 avro-builder/tests/codegen-111/src/main/avro/vs111/IntsAndLongs.avsc delete mode 100644 avro-builder/tests/codegen-14/src/main/avro/vs14/IntsAndLongs.avsc delete mode 100644 avro-builder/tests/codegen-15/src/main/avro/vs15/IntsAndLongs.avsc delete mode 100644 avro-builder/tests/codegen-16/src/main/avro/vs16/IntsAndLongs.avsc delete mode 100644 avro-builder/tests/codegen-17/src/main/avro/vs17/IntsAndLongs.avsc delete mode 100644 avro-builder/tests/codegen-18/src/main/avro/vs18/IntsAndLongs.avsc delete mode 100644 avro-builder/tests/codegen-19/src/main/avro/vs19/IntsAndLongs.avsc diff --git a/avro-builder/tests/codegen-110/src/main/avro/vs110/IntsAndLongs.avsc b/avro-builder/tests/codegen-110/src/main/avro/vs110/IntsAndLongs.avsc deleted file mode 100644 index bc4541bad..000000000 --- a/avro-builder/tests/codegen-110/src/main/avro/vs110/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "vs110", - "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 - } - ] -} \ No newline at end of file diff --git a/avro-builder/tests/codegen-111/src/main/avro/vs111/IntsAndLongs.avsc b/avro-builder/tests/codegen-111/src/main/avro/vs111/IntsAndLongs.avsc deleted file mode 100644 index 94d6b271a..000000000 --- a/avro-builder/tests/codegen-111/src/main/avro/vs111/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "vs111", - "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 - } - ] -} \ No newline at end of file diff --git a/avro-builder/tests/codegen-14/src/main/avro/vs14/IntsAndLongs.avsc b/avro-builder/tests/codegen-14/src/main/avro/vs14/IntsAndLongs.avsc deleted file mode 100644 index 94a330a5d..000000000 --- a/avro-builder/tests/codegen-14/src/main/avro/vs14/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "vs14", - "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 - } - ] -} \ No newline at end of file diff --git a/avro-builder/tests/codegen-15/src/main/avro/vs15/IntsAndLongs.avsc b/avro-builder/tests/codegen-15/src/main/avro/vs15/IntsAndLongs.avsc deleted file mode 100644 index ececfcb68..000000000 --- a/avro-builder/tests/codegen-15/src/main/avro/vs15/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "vs15", - "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 - } - ] -} \ No newline at end of file diff --git a/avro-builder/tests/codegen-16/src/main/avro/vs16/IntsAndLongs.avsc b/avro-builder/tests/codegen-16/src/main/avro/vs16/IntsAndLongs.avsc deleted file mode 100644 index 5243ce289..000000000 --- a/avro-builder/tests/codegen-16/src/main/avro/vs16/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "vs16", - "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 - } - ] -} \ No newline at end of file diff --git a/avro-builder/tests/codegen-17/src/main/avro/vs17/IntsAndLongs.avsc b/avro-builder/tests/codegen-17/src/main/avro/vs17/IntsAndLongs.avsc deleted file mode 100644 index 93248975a..000000000 --- a/avro-builder/tests/codegen-17/src/main/avro/vs17/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "vs17", - "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 - } - ] -} \ No newline at end of file diff --git a/avro-builder/tests/codegen-18/src/main/avro/vs18/IntsAndLongs.avsc b/avro-builder/tests/codegen-18/src/main/avro/vs18/IntsAndLongs.avsc deleted file mode 100644 index 143afa554..000000000 --- a/avro-builder/tests/codegen-18/src/main/avro/vs18/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "vs18", - "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 - } - ] -} \ No newline at end of file diff --git a/avro-builder/tests/codegen-19/src/main/avro/vs19/IntsAndLongs.avsc b/avro-builder/tests/codegen-19/src/main/avro/vs19/IntsAndLongs.avsc deleted file mode 100644 index 535c58b02..000000000 --- a/avro-builder/tests/codegen-19/src/main/avro/vs19/IntsAndLongs.avsc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "record", - "namespace": "vs19", - "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 - } - ] -} \ No newline at end of file diff --git a/avro-builder/tests/tests-allavro/src/test/java/com/linkedin/avroutil1/builder/SpecificRecordTest.java b/avro-builder/tests/tests-allavro/src/test/java/com/linkedin/avroutil1/builder/SpecificRecordTest.java index ba559044f..d94729673 100644 --- a/avro-builder/tests/tests-allavro/src/test/java/com/linkedin/avroutil1/builder/SpecificRecordTest.java +++ b/avro-builder/tests/tests-allavro/src/test/java/com/linkedin/avroutil1/builder/SpecificRecordTest.java @@ -16,7 +16,6 @@ import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.nio.file.Files; @@ -117,16 +116,8 @@ private Object[][] TestRoundTripSerializationProvider() { {vs110.BuilderTester.class, vs110.BuilderTester.getClassSchema()}, {vs111.BuilderTester.class, vs111.BuilderTester.getClassSchema()}, - {vs14.IntsAndLongs.class, vs14.IntsAndLongs.getClassSchema()}, - {vs15.IntsAndLongs.class, vs15.IntsAndLongs.getClassSchema()}, - {vs16.IntsAndLongs.class, vs16.IntsAndLongs.getClassSchema()}, - {vs17.IntsAndLongs.class, vs17.IntsAndLongs.getClassSchema()}, - {vs18.IntsAndLongs.class, vs18.IntsAndLongs.getClassSchema()}, - {vs19.IntsAndLongs.class, vs19.IntsAndLongs.getClassSchema()}, - {vs110.IntsAndLongs.class, vs110.IntsAndLongs.getClassSchema()}, - {vs111.IntsAndLongs.class, vs111.IntsAndLongs.getClassSchema()}, - {charseqmethod.TestCollections.class, charseqmethod.TestCollections.getClassSchema()} + // {vs14.ThousandField.class, vs14.ThousandField.getClassSchema()}, // {vs19.ThousandField.class, vs19.ThousandField.getClassSchema()} }; @@ -1620,7 +1611,7 @@ public void testRecordWitNoSimpleStrConstructor(Class clazz) { List constructors = Arrays.stream(clazz.getConstructors()).filter(constructor -> constructor.getParameters().length != 0).collect( Collectors.toList()); - Assert.assertEquals(constructors.size(), 2); + Assert.assertEquals(constructors.size(), 1); } private void assertNotSameIfNotNull(Object obj1, Object obj2) { @@ -1974,120 +1965,6 @@ public void testIfSerializable() throws IOException { } } - @DataProvider - private Object[][] intsAndLongsDataProvider() { - return new Object[][]{ - {vs14.IntsAndLongs.class}, - {vs15.IntsAndLongs.class}, - {vs16.IntsAndLongs.class}, - {vs17.IntsAndLongs.class}, - {vs18.IntsAndLongs.class}, - {vs19.IntsAndLongs.class}, - {vs110.IntsAndLongs.class}, - {vs111.IntsAndLongs.class}, - }; - } - - @Test(dataProvider = "intsAndLongsDataProvider") - public void testIntLongRecords(Class clazz) throws Exception { - // Get the newBuilder method via reflection to work with different versions - Method newBuilderMethod = clazz.getMethod("newBuilder"); - Object builder = newBuilderMethod.invoke(null); - - // Get the builder methods via reflection - Method setIntFieldMethod = builder.getClass().getMethod("setIntField", int.class); - Method setIntFieldLongMethod = builder.getClass().getMethod("setIntField", long.class); - Method setLongFieldMethod = builder.getClass().getMethod("setLongField", long.class); - Method setLongFieldIntMethod = builder.getClass().getMethod("setLongField", int.class); - Method buildMethod = builder.getClass().getMethod("build"); - - // Case 1: Set int and long with matching int/long types (primitive) - Object builder1 = newBuilderMethod.invoke(null); - setIntFieldMethod.invoke(builder1, 123); - setLongFieldMethod.invoke(builder1, 456L); - Object record1 = buildMethod.invoke(builder1); - - // Case 2: Set int field with long type, and long field with int type (primitive) - Object builder2 = newBuilderMethod.invoke(null); - setIntFieldLongMethod.invoke(builder2, 123L); - setLongFieldIntMethod.invoke(builder2, 456); - Object record2 = buildMethod.invoke(builder2); - - // Case 3: Using the put method with primitive types - Object record3 = clazz.newInstance(); - Method putMethod = clazz.getMethod("put", int.class, Object.class); - putMethod.invoke(record3, 0, 456L); // longField with long - putMethod.invoke(record3, 1, 123); // intField with int - - // Case 4: Using the put method with cross types - Object record4 = clazz.newInstance(); - putMethod.invoke(record4, 0, 456); // longField with int - putMethod.invoke(record4, 1, 123L); // intField with long - - // Case 5: Using the put method with Integer/Long wrapper classes - Object record5 = clazz.newInstance(); - putMethod.invoke(record5, 0, Integer.valueOf(456)); // longField with Integer - putMethod.invoke(record5, 1, Long.valueOf(123L)); // intField with Long - - // Verify all records are equal - Assert.assertEquals(record1, record2); - Assert.assertEquals(record1, record3); - Assert.assertEquals(record1, record4); - Assert.assertEquals(record1, record5); - - // Verify field values directly - Method getIntFieldMethod = clazz.getMethod("getIntField"); - Method getLongFieldMethod = clazz.getMethod("getLongField"); - - Assert.assertEquals(123, getIntFieldMethod.invoke(record1)); - Assert.assertEquals(456L, getLongFieldMethod.invoke(record1)); - Assert.assertEquals(123, getIntFieldMethod.invoke(record5)); - Assert.assertEquals(456L, getLongFieldMethod.invoke(record5)); - } - - @Test(dataProvider = "intsAndLongsDataProvider") - public void testIntFieldOutOfBoundsHandling(Class clazz) throws Exception { - // Test Case 1: Using builder pattern with setIntField(long) - Method newBuilderMethod = clazz.getMethod("newBuilder"); - Object builder = newBuilderMethod.invoke(null); - Method setIntFieldLongMethod = builder.getClass().getMethod("setIntField", long.class); - Method buildMethod = builder.getClass().getMethod("build"); - - try { - // Try to set a long value that's too large for an int - setIntFieldLongMethod.invoke(builder, Long.MAX_VALUE); - // This should throw an exception when we try to build - buildMethod.invoke(builder); - Assert.fail("Expected an exception when building with out-of-bounds long value"); - } catch (InvocationTargetException e) { - Throwable cause = e.getCause(); - Assert.assertNotNull(cause, "Exception cause should not be null"); - Assert.assertTrue(cause instanceof AvroRuntimeException, - "Expected AvroRuntimeException but got: " + cause.getClass().getName()); - Assert.assertTrue(cause.getMessage() != null && - cause.getMessage().contains(String.format("Long value %d cannot be cast to int", Long.MAX_VALUE)), - "Expected message about long value cast but got: " + cause.getMessage()); - } - - // Test Case 2: Using put method directly - Object record = clazz.newInstance(); - Method putMethod = clazz.getMethod("put", int.class, Object.class); - - try { - // Try to put a long value that's too large for the int field (field index 1) - putMethod.invoke(record, 1, Long.MAX_VALUE); - Assert.fail("Expected an exception when putting out-of-bounds long value"); - } catch (InvocationTargetException e) { - Throwable cause = e.getCause(); - Assert.assertNotNull(cause, "Exception cause should not be null"); - Assert.assertTrue(cause instanceof AvroRuntimeException, - "Expected AvroRuntimeException but got: " + cause.getClass().getName()); - Assert.assertTrue(cause.getMessage() != null && - cause.getMessage().contains(String.format("Long value %d cannot be cast to int", Long.MAX_VALUE)), - "Expected message about long value cast but got: " + cause.getMessage()); - } - } - /** * Tests that both String and UTF8 fields are supported in the generated classes and can be accessed * interchangeably directly and through getters. diff --git a/avro-codegen/src/main/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGenerator.java b/avro-codegen/src/main/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGenerator.java index deeba0f64..5e5ca3b17 100644 --- a/avro-codegen/src/main/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGenerator.java +++ b/avro-codegen/src/main/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGenerator.java @@ -56,6 +56,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; + /** * generates java classes out of avro schemas. */ @@ -461,10 +462,6 @@ protected JavaFile generateSpecificRecord(AvroRecordSchema recordSchema, Specifi // add all arg constructor if #args < 254 addAllArgsConstructor(recordSchema, config.getDefaultMethodStringRepresentation(), classBuilder, !config.isUtf8EncodingEnabled()); - // Add numeric conversion constructors. This will add constructor using Integer param for long/Long fields, and - // Long param for int/Integer fields. Note that this will not create all constructor permutations - addNumericConversionConstructors(recordSchema, config, classBuilder); - if (config.isUtf8EncodingEnabled() && SpecificRecordGeneratorUtil.recordHasSimpleStringField(recordSchema)) { addAllArgsConstructor(recordSchema, config.getDefaultMethodStringRepresentation().equals(AvroJavaStringRepresentation.STRING) @@ -491,10 +488,6 @@ protected JavaFile generateSpecificRecord(AvroRecordSchema recordSchema, Specifi if(config.isUtf8EncodingEnabled() && overloadedSetterIfString != null) { classBuilder.addMethod(getOverloadedSetterSpecIfStringField(field, config)); } - MethodSpec overloadedSetterIfIntOrLong = getOverloadedSetterSpecIfIntOrLongField(field, config); - if (overloadedSetterIfIntOrLong != null) { - classBuilder.addMethod(overloadedSetterIfIntOrLong); - } } } @@ -617,127 +610,6 @@ private void addAllArgsConstructor(AvroRecordSchema recordSchema, } } - /** - * Adds a constructor that performs numeric conversions for {@link Integer} and {@link Long} fields. - * @param recordSchema The Avro record schema. - * @param config The {@link SpecificRecordGenerationConfig} - * @param classBuilder The {@link TypeSpec.Builder} for the generated class. - */ - private void addNumericConversionConstructors(AvroRecordSchema recordSchema, SpecificRecordGenerationConfig config, - TypeSpec.Builder classBuilder) { - // Only proceed if we have fewer than 254 fields (Java method parameter limit) - if (recordSchema.getFields().size() >= 254) { - return; - } - - // If the record has any Integer/int or Long/long fields - // Add a constructor that handles both types of conversions - if (hasIntOrLongField(recordSchema)) { - addMixedNumericConversionConstructor(recordSchema, config, classBuilder); - } - } - - /** - * Checks if the record schema has int or long field. - * - * @param recordSchema The record schema to check - * @return True if the schema has at least one int or long field - */ - private boolean hasIntOrLongField(AvroRecordSchema recordSchema) { - for (AvroSchemaField field : recordSchema.getFields()) { - // Check if field is long or [null, long] - if (SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.LONG, field.getSchema()) - || SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.INT, field.getSchema())) { - return true; - } - } - return false; - } - - /** - * Adds a constructor that handles both Integer-to-Long and Long-to-Integer conversions simultaneously - * - * @param recordSchema The record schema - * @param config The {@link SpecificRecordGenerationConfig} - * @param classBuilder The class builder to add the constructor to - */ - private void addMixedNumericConversionConstructor(AvroRecordSchema recordSchema, SpecificRecordGenerationConfig config, - TypeSpec.Builder classBuilder) { - MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder() - .addModifiers(Modifier.PUBLIC); - - for (AvroSchemaField field : recordSchema.getFields()) { - String escapedFieldName = getFieldNameWithSuffix(field); - - if (SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.LONG, field.getSchema()) - || SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.INT, field.getSchema())) { - - // Get the class from the schema - Class fieldClass = - SpecificRecordGeneratorUtil.getJavaClassForAvroTypeIfApplicable(field.getSchemaOrRef().getSchema().type(), - config.getDefaultMethodStringRepresentation(), false); - - if(fieldClass != null ) { - if (fieldClass.equals(long.class)) { - // For primitive long, throw exception on null or use default value - constructorBuilder - .addParameter(ParameterSpec.builder(Integer.class, escapedFieldName).build()) - .beginControlFlow("if ($L != null)", escapedFieldName) - .addStatement("this.$1L = $1L.longValue()", escapedFieldName) - .nextControlFlow("else") - .addStatement("throw new org.apache.avro.AvroRuntimeException(\"$L cannot be set to null\")", escapedFieldName) - .endControlFlow(); - } else if (fieldClass.equals(int.class)) { - // For primitive int, throw exception on null or use default value - constructorBuilder - .addParameter(ParameterSpec.builder(Long.class, escapedFieldName).build()) - .beginControlFlow("if ($L != null)", escapedFieldName) - .beginControlFlow("if ($1L <= Integer.MAX_VALUE && $1L >= Integer.MIN_VALUE)", escapedFieldName) - .addStatement("this.$1L = $1L.intValue()", escapedFieldName) - .nextControlFlow("else") - .addStatement("throw new org.apache.avro.AvroRuntimeException(\"Long value \" + $L + \" cannot be cast to int\")", escapedFieldName) - .endControlFlow() - .nextControlFlow("else") - .addStatement("throw new org.apache.avro.AvroRuntimeException(\"$L cannot be set to null\")", escapedFieldName) - .endControlFlow(); - } - } else if (field.getSchema() != null && field.getSchema().type().equals(AvroType.UNION)) { - TypeName typeName = SpecificRecordGeneratorUtil.getTypeName(field.getSchemaOrRef().getSchema(), - field.getSchemaOrRef().getSchema().type(), true, - config.getDefaultMethodStringRepresentation()); - if (typeName.equals(ClassName.get(Long.class))) { - constructorBuilder - .addParameter(ParameterSpec.builder(Integer.class, escapedFieldName).build()) - .beginControlFlow("if ($L != null)", escapedFieldName) - .addStatement("this.$1L = Long.valueOf($1L)", escapedFieldName) - .nextControlFlow("else") - .addStatement("this.$L = null", escapedFieldName) - .endControlFlow(); - } else if (typeName.equals(ClassName.get(Integer.class))) { - constructorBuilder - .addParameter(ParameterSpec.builder(Long.class, escapedFieldName).build()) - .beginControlFlow("if ($L != null)", escapedFieldName) - .beginControlFlow("if ($1L <= Integer.MAX_VALUE && $1L >= Integer.MIN_VALUE)", escapedFieldName) - .addStatement("this.$1L = $1L.intValue()", escapedFieldName) - .nextControlFlow("else") - .addStatement("throw new org.apache.avro.AvroRuntimeException(\"Long value \" + $L + \" cannot be cast to Integer\")", escapedFieldName) - .endControlFlow() - .nextControlFlow("else") - .addStatement("this.$L = null", escapedFieldName) - .endControlFlow(); - } - } - } else { - // For other fields, use their normal type - constructorBuilder.addParameter( - getParameterSpecForField(field, AvroJavaStringRepresentation.STRING)); - constructorBuilder.addStatement("this.$1L = $1L", escapedFieldName); - - } - } - classBuilder.addMethod(constructorBuilder.build()); - } - private String replaceSingleDollarSignWithDouble(String str) { if (str != null && str.contains("$")) { str = SpecificRecordGeneratorUtil.SINGLE_DOLLAR_SIGN_REGEX.matcher(str).replaceAll("\\$\\$"); @@ -878,7 +750,7 @@ private void populateBuilderClassBuilder(TypeSpec.Builder recordBuilder, AvroRec // get, set, has, clear methods populateAccessorMethodsBlock(accessorMethodSpecs, field, fieldClass, fieldType, recordSchema.getFullName(), - fieldIndex, config); + fieldIndex); fieldIndex++; } @@ -980,7 +852,7 @@ private List getNewBuilderMethods(AvroRecordSchema recordSchema) { } private void populateAccessorMethodsBlock(List accessorMethodSpecs, AvroSchemaField field, - Class fieldClass, TypeName fieldType, String parentClass, int fieldIndex, SpecificRecordGenerationConfig config) { + Class fieldClass, TypeName fieldType, String parentClass, int fieldIndex) { String escapedFieldName = getFieldNameWithSuffix(field); //Getter MethodSpec.Builder getMethodBuilder = @@ -1012,14 +884,6 @@ private void populateAccessorMethodsBlock(List accessorMethodSpecs, setMethodBuilder.addParameter(fieldType, "value"); } - // Additional setter for int/long fields (both primitive and boxed) - MethodSpec.Builder overloadSetMethodBuilder = null; - if ((SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.LONG, field.getSchema()) - || SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.INT, field.getSchema()))) { - overloadSetMethodBuilder = createNumericTypeOverloadedSetterBuilder(fieldClass, field, escapedFieldName, fieldIndex, - parentClass, config); - } - // Has MethodSpec.Builder hasMethodBuilder = MethodSpec.methodBuilder(getMethodNameForFieldWithPrefix("has", field.getName())) @@ -1047,95 +911,10 @@ private void populateAccessorMethodsBlock(List accessorMethodSpecs, accessorMethodSpecs.add(getMethodBuilder.build()); accessorMethodSpecs.add(setMethodBuilder.build()); - if (overloadSetMethodBuilder != null) { - accessorMethodSpecs.add(overloadSetMethodBuilder.build()); - } accessorMethodSpecs.add(hasMethodBuilder.build()); accessorMethodSpecs.add(clearMethodBuilder.build()); } - /** - * Creates an overloaded setter method for numeric type fields (int/long) to support flexible type conversion. - * - * @param fieldClass The class of the field (int.class, long.class, Integer.class, or Long.class) - * @param field The Avro schema field - * @param escapedFieldName The escaped field name - * @param fieldIndex The index of the field - * @param parentClass The parent class name - * @return A MethodSpec.Builder for the overloaded setter method - */ - private MethodSpec.Builder createNumericTypeOverloadedSetterBuilder(Class fieldClass, AvroSchemaField field, - String escapedFieldName, int fieldIndex, String parentClass, SpecificRecordGenerationConfig config) { - MethodSpec.Builder methodBuilder = null; - - if (fieldClass != null) { - methodBuilder = MethodSpec - .methodBuilder(getMethodNameForFieldWithPrefix("set", escapedFieldName)) - .addModifiers(Modifier.PUBLIC); - - if (fieldClass.equals(long.class)) { - // If field is of type long, add method with int parameter - methodBuilder - .addParameter(TypeName.INT, "value") - .addStatement("validate(fields()[$L], value)", fieldIndex) - .addStatement("this.$L = value", escapedFieldName); - - } else if (fieldClass.equals(int.class)) { - // If field is of type int, add method with long parameter - // This method will error if the long input is greater than Integer.MAX_VALUE - methodBuilder - .addParameter(TypeName.LONG, "value") - .beginControlFlow("if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE)") - .addStatement("this.$L = (int) value", escapedFieldName) - .nextControlFlow("else") - .addStatement("throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to int\")") - .endControlFlow(); - - } - } else if (field.getSchema() != null && field.getSchema().type().equals(AvroType.UNION)) { - TypeName typeName = SpecificRecordGeneratorUtil.getTypeName(field.getSchemaOrRef().getSchema(), - field.getSchemaOrRef().getSchema().type(), true, - config.getDefaultMethodStringRepresentation()); - - if (typeName.equals(ClassName.get(Long.class))) { - // For long or Long fields, add a setter that accepts int or Integer - methodBuilder = MethodSpec - .methodBuilder(getMethodNameForFieldWithPrefix("set", escapedFieldName)) - .addParameter(ClassName.get(Integer.class), "value") - .beginControlFlow("if (value == null)") - .addStatement("this.$L = null", escapedFieldName) - .nextControlFlow("else") - .addStatement("this.$L = Long.valueOf(value)", escapedFieldName) - .endControlFlow(); - - - } else if (typeName.equals(ClassName.get(Integer.class))) { - methodBuilder = MethodSpec - .methodBuilder(getMethodNameForFieldWithPrefix("set", escapedFieldName)) - .addParameter(ClassName.get(Long.class), "value") - .beginControlFlow("if (value == null)") - .addStatement("this.$L = null", escapedFieldName) - .nextControlFlow("else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE)") - .addStatement("this.$L = Integer.valueOf(value.intValue())", escapedFieldName) - .nextControlFlow("else") - .addStatement("throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to Integer\")") - .endControlFlow(); - } - } - - if (methodBuilder != null) { - methodBuilder - .addJavadoc( - "Sets the value of the '$L' field.$L" + "@param value The value of '$L'.\n" + "@return This Builder.", - field.getName(), getFieldJavaDoc(field), field.getName()) - .addStatement("fieldSetFlags()[$L] = true", fieldIndex) - .addStatement("return this") - .returns(ClassName.get(parentClass, "Builder")); - } - - return methodBuilder; - } - private String getFieldJavaDoc(AvroSchemaField field) { return (field.hasDoc() ? "\n" + replaceSingleDollarSignWithDouble(field.getDoc()) + "\n" : "\n"); } @@ -1186,14 +965,11 @@ private void addCustomDecodeMethod(MethodSpec.Builder customDecodeBuilder, AvroR for (; fieldCounter < Math.min(blockSize * chunkCounter + blockSize, recordSchema.getFields().size()); fieldCounter++) { AvroSchemaField field = recordSchema.getField(fieldCounter); - boolean unionContainsBothIntAndLong = field.getSchemaOrRef().getSchema().type().equals(AvroType.UNION) && - unionSchemaContainsBothIntAndLong((AvroUnionSchema) field.getSchemaOrRef().getSchema()); - String escapedFieldName = getFieldNameWithSuffix(field); customDecodeChunkMethod.addStatement(getSerializedCustomDecodeBlock(config, field.getSchemaOrRef().getSchema(), field.getSchemaOrRef().getSchema().type(), "this." + replaceSingleDollarSignWithDouble(escapedFieldName), "this." + replaceSingleDollarSignWithDouble(escapedFieldName), StringUtils.EMPTY_STRING, - sizeValCounter, unionContainsBothIntAndLong)); + sizeValCounter)); } chunkCounter++; classBuilder.addMethod(customDecodeChunkMethod.build()); @@ -1225,13 +1001,11 @@ private void addCustomDecodeMethod(MethodSpec.Builder customDecodeBuilder, AvroR fieldCounter++) { AvroSchemaField field = recordSchema.getField(fieldCounter); String escapedFieldName = getFieldNameWithSuffix(field); - boolean unionContainsBothIntAndLong = field.getSchemaOrRef().getSchema().type().equals(AvroType.UNION) && - unionSchemaContainsBothIntAndLong((AvroUnionSchema) field.getSchemaOrRef().getSchema()); customDecodeChunkMethod.addStatement(String.format("case %s: ",fieldIndex++)+ getSerializedCustomDecodeBlock(config, field.getSchemaOrRef().getSchema(), field.getSchemaOrRef().getSchema().type(), "this." + replaceSingleDollarSignWithDouble(escapedFieldName), "this." + replaceSingleDollarSignWithDouble(escapedFieldName), StringUtils.EMPTY_STRING, - sizeValCounter, unionContainsBothIntAndLong)) + sizeValCounter)) .addStatement("break"); } customDecodeChunkMethod @@ -1251,7 +1025,7 @@ private void addCustomDecodeMethod(MethodSpec.Builder customDecodeBuilder, AvroR private String getSerializedCustomDecodeBlock(SpecificRecordGenerationConfig config, AvroSchema fieldSchema, AvroType fieldType, String fieldName, String schemaFieldName, String arrayOption, - Counter sizeValCounter, boolean unionContainsBothIntAndLong) { + Counter sizeValCounter) { String serializedCodeBlock = ""; CodeBlock.Builder codeBlockBuilder = CodeBlock.builder(); switch (fieldType) { @@ -1263,45 +1037,13 @@ private String getSerializedCustomDecodeBlock(SpecificRecordGenerationConfig con serializedCodeBlock = String.format("%s = in.readBoolean()", fieldName); break; case INT: - if (unionContainsBothIntAndLong) { - serializedCodeBlock = String.format("%s = in.readInt()", fieldName); - } else { - String cleanIntFieldName = fieldName.replaceAll("^this\\.", ""); - String tempIntVarName = "temp" + Character.toUpperCase(cleanIntFieldName.charAt(0)) + cleanIntFieldName.substring(1); - codeBlockBuilder - .beginControlFlow("try") - .addStatement("$L = in.readInt()", fieldName) - .nextControlFlow("catch (Exception e)") - .addStatement("// If int decoding fails, try long decoding with bounds check") - .addStatement("long $L = in.readLong()", tempIntVarName) - .beginControlFlow("if ($L <= Integer.MAX_VALUE && $L >= Integer.MIN_VALUE)", tempIntVarName, tempIntVarName) - .addStatement("$L = (int) $L", fieldName, tempIntVarName) - .nextControlFlow("else") - .addStatement("throw new org.apache.avro.AvroRuntimeException(\"Long value cannot be cast to int\")") - .endControlFlow() - .endControlFlow(); - serializedCodeBlock = codeBlockBuilder.build().toString(); - } + serializedCodeBlock = String.format("%s = in.readInt()", fieldName); break; case FLOAT: serializedCodeBlock = String.format("%s = in.readFloat()", fieldName); break; case LONG: - if (unionContainsBothIntAndLong) { - serializedCodeBlock = String.format("%s = in.readLong()", fieldName); - } else { - String cleanLongFieldName = fieldName.replaceAll("^this\\.", ""); - String tempLongVarName = - "temp" + Character.toUpperCase(cleanLongFieldName.charAt(0)) + cleanLongFieldName.substring(1); - codeBlockBuilder.beginControlFlow("try") - .addStatement("$L = in.readLong()", fieldName) - .nextControlFlow("catch (Exception e)") - .addStatement("// If long decoding fails, try int decoding with conversion to long") - .addStatement("int $L = in.readInt()", tempLongVarName) - .addStatement("$L = (long) $L", fieldName, tempLongVarName) - .endControlFlow(); - serializedCodeBlock = codeBlockBuilder.build().toString(); - } + serializedCodeBlock = String.format("%s = in.readLong()", fieldName); break; case DOUBLE: serializedCodeBlock = String.format("%s = in.readDouble()", fieldName); @@ -1370,7 +1112,7 @@ private String getSerializedCustomDecodeBlock(SpecificRecordGenerationConfig con codeBlockBuilder.addStatement( getSerializedCustomDecodeBlock(config, arrayItemSchema, arrayItemSchema.type(), arrayElementVarName, schemaFieldName, arrayOption + SpecificRecordGeneratorUtil.ARRAY_GET_ELEMENT_TYPE, - sizeValCounter, unionContainsBothIntAndLong)); + sizeValCounter)); codeBlockBuilder.addStatement("$L.add($L)", arrayVarName, arrayElementVarName) .endControlFlow() .endControlFlow() @@ -1413,12 +1155,12 @@ private String getSerializedCustomDecodeBlock(SpecificRecordGenerationConfig con .addStatement( getSerializedCustomDecodeBlock(config, ((AvroMapSchema) fieldSchema).getValueSchema(), AvroType.STRING, mapKeyVarName, schemaFieldName, arrayOption + SpecificRecordGeneratorUtil.MAP_GET_VALUE_TYPE, - sizeValCounter, unionContainsBothIntAndLong)) + sizeValCounter)) .addStatement("$T $L = null", ((mapItemClass != null) ? mapItemClass : mapItemClassName), mapValueVarName) .addStatement(getSerializedCustomDecodeBlock(config, ((AvroMapSchema) fieldSchema).getValueSchema(), ((AvroMapSchema) fieldSchema).getValueSchema().type(), mapValueVarName, schemaFieldName, arrayOption + SpecificRecordGeneratorUtil.MAP_GET_VALUE_TYPE, - sizeValCounter, unionContainsBothIntAndLong)); + sizeValCounter)); codeBlockBuilder.addStatement("$L.put($L,$L)", mapVarName, mapKeyVarName, mapValueVarName) .endControlFlow() @@ -1440,7 +1182,7 @@ private String getSerializedCustomDecodeBlock(SpecificRecordGenerationConfig con codeBlockBuilder.addStatement( getSerializedCustomDecodeBlock(config, unionMember.getSchema(), unionMember.getSchema().type(), fieldName, schemaFieldName, arrayOption + ".getTypes().get(" + i + ")", - sizeValCounter, unionContainsBothIntAndLong)); + sizeValCounter)); if (unionMember.getSchema().type().equals(AvroType.NULL)) { codeBlockBuilder.addStatement("$L = null", fieldName); } @@ -1745,13 +1487,11 @@ private void addPutByIndexMethod(TypeSpec.Builder classBuilder, AvroRecordSchema field.getSchemaOrRef().getSchema().type(), true, config.getDefaultFieldStringRepresentation())); } } else if (field.getSchema() != null && AvroType.UNION.equals(field.getSchema().type())) { - switchBuilder.add("case $L: \n", fieldIndex++); + switchBuilder.addStatement("case $L:", fieldIndex++); switchBuilder.beginControlFlow("if (value == null)") .addStatement("this.$1L = null", escapedFieldName) .endControlFlow(); - boolean unionContainsBothIntAndLong = unionSchemaContainsBothIntAndLong((AvroUnionSchema) field.getSchema()); - // if union might contain string value in runtime for (SchemaOrRef unionMemberSchema : ((AvroUnionSchema) field.getSchema()).getTypes()) { if (config.isUtf8EncodingInPutByIndexEnabled() && SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.STRING, unionMemberSchema.getSchema())) { @@ -1796,22 +1536,6 @@ private void addPutByIndexMethod(TypeSpec.Builder classBuilder, AvroRecordSchema field.getSchemaOrRef().getSchema().type(), true, config.getDefaultFieldStringRepresentation())); } switchBuilder.endControlFlow(); - } else if (!unionContainsBothIntAndLong && SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.INT, unionMemberSchema.getSchema())) { - // For boxed Integer fields, handle Long values with bounds check - switchBuilder - .beginControlFlow("else if (value instanceof Long)") - .beginControlFlow("if ((Long) value <= Integer.MAX_VALUE && (Long) value >= Integer.MIN_VALUE)") - .addStatement("this.$1L = ((Long) value).intValue()", escapedFieldName) - .nextControlFlow("else") - .addStatement("throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to Integer\")") - .endControlFlow() - .endControlFlow(); - } else if (!unionContainsBothIntAndLong && SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.LONG, unionMemberSchema.getSchema())) { - // For boxed Long fields, handle Integer values - switchBuilder.beginControlFlow("else if (value instanceof Integer)") - .addStatement("this.$1L = ((Integer) value).longValue()", escapedFieldName) - .addStatement("break") - .endControlFlow(); } } switchBuilder.beginControlFlow("else") @@ -1820,33 +1544,6 @@ private void addPutByIndexMethod(TypeSpec.Builder classBuilder, AvroRecordSchema field.getSchemaOrRef().getSchema().type(), true, config.getDefaultFieldStringRepresentation())) .endControlFlow() .addStatement("break"); - } else if (fieldClass == int.class) { - // If the field is an int, allow input value to be a long with range check - switchBuilder - .add("case $L: \n", fieldIndex++) - .beginControlFlow("if (value instanceof Long)") - .beginControlFlow("if (((Long) value) <= Integer.MAX_VALUE && ((Long) value) >= Integer.MIN_VALUE)") - .addStatement("this.$1L = ((Long) value).intValue()", escapedFieldName) - .endControlFlow() - .beginControlFlow("else") - .addStatement("throw new org.apache.avro.AvroRuntimeException(\"Long value \" + value + \" cannot be cast to int\")") - .endControlFlow() - .endControlFlow() - .beginControlFlow("else") - .addStatement("this.$L = ($T) value", escapedFieldName, fieldClass) - .endControlFlow() - .addStatement("break"); - } else if (fieldClass == long.class) { - // If the field is a long, allow input value to be an int - switchBuilder - .add("case $L: \n", fieldIndex++) - .beginControlFlow("if (value instanceof Integer)") - .addStatement("this.$1L = ((Integer) value).longValue()", escapedFieldName) - .endControlFlow() - .beginControlFlow("else") - .addStatement("this.$L = ($T) value", escapedFieldName, fieldClass) - .endControlFlow() - .addStatement("break"); } else { switchBuilder.addStatement( fieldClass != null ? "case $L: this.$L = ($T) value; break" : "case $L: this.$L = ($L) value; break", @@ -1917,6 +1614,7 @@ private void addGetByIndexMethod(TypeSpec.Builder classBuilder, AvroRecordSchema .endControlFlow(); } } + switchBuilder.beginControlFlow("else") .addStatement("return this.$1L", escapedFieldName) .endControlFlow(); @@ -1930,103 +1628,6 @@ private void addGetByIndexMethod(TypeSpec.Builder classBuilder, AvroRecordSchema classBuilder.addMethod(methodSpecBuilder.addCode(switchBuilder.build()).build()); } - private boolean unionSchemaContainsBothIntAndLong(AvroUnionSchema unionSchema) { - boolean unionContainsInt = false; - boolean unionContainsLong = false; - for (SchemaOrRef unionMemberSchema : unionSchema.getTypes()) { - if (SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.INT, unionMemberSchema.getSchema())) { - unionContainsInt = true; - } - if (SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.LONG, unionMemberSchema.getSchema())) { - unionContainsLong = true; - } - } - return unionContainsInt && unionContainsLong; - } - - private MethodSpec getOverloadedSetterSpecIfIntOrLongField(AvroSchemaField field, - SpecificRecordGenerationConfig config) { - MethodSpec.Builder numberSetter = null; - String escapedFieldName = getFieldNameWithSuffix(field); - if (SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.LONG, field.getSchema()) - || SpecificRecordGeneratorUtil.isNullUnionOf(AvroType.INT, field.getSchema())) { - - // Get the class from the schema - Class fieldClass = - SpecificRecordGeneratorUtil.getJavaClassForAvroTypeIfApplicable(field.getSchemaOrRef().getSchema().type(), - config.getDefaultMethodStringRepresentation(), false); - - numberSetter = MethodSpec - .methodBuilder(getMethodNameForFieldWithPrefix("set", escapedFieldName)) - .addModifiers(Modifier.PUBLIC); - - if(fieldClass != null ) { - if (fieldClass.equals(long.class)) { - // If field is of type long, add method with int parameter - // int value can safely be cast to long - numberSetter - .addParameter( int.class, escapedFieldName) - .addStatement("this.$1L = (long) $1L", escapedFieldName); - } else if (fieldClass.equals(int.class)) { - // If field is of type int, add method with long parameter - // This method will error if the long input is greater than Integer.MAX_VALUE - - CodeBlock castToInt = CodeBlock - .builder() - .beginControlFlow("if ($1L <= Integer.MAX_VALUE && $1L >= Integer.MIN_VALUE)", escapedFieldName) - .addStatement("this.$1L = (int) $1L", escapedFieldName) - .endControlFlow() - .beginControlFlow("else") - .addStatement("throw new org.apache.avro.AvroRuntimeException(\"long value cannot be cast to int\")") - .endControlFlow() - .build(); - - numberSetter - .addParameter(long.class, escapedFieldName) - .addCode(castToInt); - } - - } else if (field.getSchema() != null && field.getSchema().type().equals(AvroType.UNION)) { - TypeName typeName = SpecificRecordGeneratorUtil.getTypeName(field.getSchemaOrRef().getSchema(), - field.getSchemaOrRef().getSchema().type(), true, - config.getDefaultMethodStringRepresentation()); - - CodeBlock nullCheck = CodeBlock.builder() - .beginControlFlow("if ($1L == null)", escapedFieldName) - .addStatement("this.$1L = null", escapedFieldName) - .endControlFlow() - .build(); - - if (typeName.equals(ClassName.get(Long.class))) { - numberSetter - .addParameter(ClassName.get(Integer.class), escapedFieldName) - .addCode(nullCheck) - .beginControlFlow("else") - .addStatement("this.$1L = Long.valueOf($1L)", escapedFieldName) - .endControlFlow(); - - } else if (typeName.equals(ClassName.get(Integer.class))) { - - CodeBlock castToInt = CodeBlock - .builder() - .beginControlFlow("else if ($1L <= Integer.MAX_VALUE && $1L >= Integer.MIN_VALUE)", escapedFieldName) - .addStatement("this.$1L = $1L.intValue()", escapedFieldName) - .endControlFlow() - .beginControlFlow("else") - .addStatement("throw new org.apache.avro.AvroRuntimeException(\"Long value cannot be cast to Integer\")") - .endControlFlow() - .build(); - - numberSetter - .addParameter(ClassName.get(Long.class), escapedFieldName) - .addCode(nullCheck) - .addCode(castToInt); - } - } - } - return numberSetter == null ? null : numberSetter.build(); - } - private MethodSpec getOverloadedSetterSpecIfStringField(AvroSchemaField field, SpecificRecordGenerationConfig config) { MethodSpec.Builder stringSetter = null; @@ -2310,6 +1911,7 @@ private ParameterSpec getParameterSpecForField(AvroSchemaField field, AvroJavaSt return parameterSpecBuilder.build(); } + private void addAndInitializeSizeFieldToClass(TypeSpec.Builder classBuilder, AvroFixedSchema fixedSchema) throws ClassNotFoundException { classBuilder.addAnnotation(AnnotationSpec.builder(SpecificRecordGeneratorUtil.CLASSNAME_FIXED_SIZE)