diff --git a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java index f6f842035fa..117fd2ed6ee 100644 --- a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java +++ b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java @@ -122,6 +122,7 @@ void addLogicalTypeConversions(SpecificData specificData) { private boolean gettersReturnOptional = false; private boolean optionalGettersForNullableFieldsOnly = false; private boolean createSetters = true; + private boolean createNullSafeAnnotations = false; private boolean createAllArgsConstructor = true; private String outputCharacterEncoding; private boolean enableDecimalLogicalType = false; @@ -262,6 +263,17 @@ public void setCreateSetters(boolean createSetters) { this.createSetters = createSetters; } + public boolean isCreateNullSafeAnnotations() { + return this.createNullSafeAnnotations; + } + + /** + * Set to true to add jetbrains @Nullable and @NotNull annotations + */ + public void setCreateNullSafeAnnotations(boolean createNullSafeAnnotations) { + this.createNullSafeAnnotations = createNullSafeAnnotations; + } + public boolean isCreateOptionalGetters() { return this.createOptionalGetters; } diff --git a/lang/java/compiler/src/main/velocity/org/apache/avro/compiler/specific/templates/java/classic/record.vm b/lang/java/compiler/src/main/velocity/org/apache/avro/compiler/specific/templates/java/classic/record.vm index 5e7f2550be7..2e3bb075961 100755 --- a/lang/java/compiler/src/main/velocity/org/apache/avro/compiler/specific/templates/java/classic/record.vm +++ b/lang/java/compiler/src/main/velocity/org/apache/avro/compiler/specific/templates/java/classic/record.vm @@ -161,7 +161,7 @@ public class ${this.mangleTypeIdentifier($schema.getName())} extends ${this.getS #end #end */ - public ${this.mangleTypeIdentifier($schema.getName())}(#foreach($field in $schema.getFields())${this.javaType($field.schema())} ${this.mangle($field.name())}#if($foreach.count < $schema.getFields().size()), #end#end) { + public ${this.mangleTypeIdentifier($schema.getName())}(#foreach($field in $schema.getFields())#if(${this.createNullSafeAnnotations})#if(${field.schema().isNullable()})@org.jetbrains.annotations.Nullable#else@org.jetbrains.annotations.NotNull#end #end${this.javaType($field.schema())} ${this.mangle($field.name())}#if($foreach.count < $schema.getFields().size()), #end#end) { #foreach ($field in $schema.getFields()) ${this.generateSetterCode($field.schema(), ${this.mangle($field.name())}, ${this.mangle($field.name())})} #end @@ -243,6 +243,13 @@ public class ${this.mangleTypeIdentifier($schema.getName())} extends ${this.getS #else * @return The value of the '${this.mangle($field.name(), $schema.isError())}' field. #end */ +#if(${this.createNullSafeAnnotations}) +#if (${field.schema().isNullable()}) + @org.jetbrains.annotations.Nullable +#else + @org.jetbrains.annotations.NotNull +#end +#end public ${this.javaUnbox($field.schema(), false)} ${this.generateGetMethod($schema, $field)}() { return ${this.mangle($field.name(), $schema.isError())}; } @@ -267,7 +274,7 @@ public class ${this.mangleTypeIdentifier($schema.getName())} extends ${this.getS #end * @param value the value to set. */ - public void ${this.generateSetMethod($schema, $field)}(${this.javaUnbox($field.schema(), false)} value) { + public void ${this.generateSetMethod($schema, $field)}(#if(${this.createNullSafeAnnotations})#if(${field.schema().isNullable()})@org.jetbrains.annotations.Nullable#else@org.jetbrains.annotations.NotNull#end #end${this.javaUnbox($field.schema(), false)} value) { ${this.generateSetterCode($field.schema(), ${this.mangle($field.name(), $schema.isError())}, "value")} } #end @@ -423,7 +430,7 @@ public class ${this.mangleTypeIdentifier($schema.getName())} extends ${this.getS * @param value The value of '${this.mangle($field.name(), $schema.isError())}'. * @return This builder. */ - public #if ($schema.getNamespace())$this.mangle($schema.getNamespace()).#end${this.mangleTypeIdentifier($schema.getName())}.Builder ${this.generateSetMethod($schema, $field)}(${this.javaUnbox($field.schema(), false)} value) { + public #if ($schema.getNamespace())$this.mangle($schema.getNamespace()).#end${this.mangleTypeIdentifier($schema.getName())}.Builder ${this.generateSetMethod($schema, $field)}(#if(${this.createNullSafeAnnotations})#if(${field.schema().isNullable()})@org.jetbrains.annotations.Nullable#else@org.jetbrains.annotations.NotNull#end #end${this.javaUnbox($field.schema(), false)} value) { validate(fields()[$field.pos()], value); #if (${this.hasBuilder($field.schema())}) this.${this.mangle($field.name(), $schema.isError())}Builder = null; diff --git a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java index e34dd1b01fd..7dc203b0357 100644 --- a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java +++ b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java @@ -182,6 +182,19 @@ public abstract class AbstractAvroMojo extends AbstractMojo { */ protected boolean createSetters; + /** + * The createNullSafeAnnotations parameter adds JetBrains {@literal @}Nullable + * and {@literal @}NotNull annotations for fhe fields of the record. The default + * is to not include annotations. + * + * @parameter property="createNullSafeAnnotations" + * + * @see + * JetBrains nullability annotations + */ + protected boolean createNullSafeAnnotations = false; + /** * A set of fully qualified class names of custom * {@link org.apache.avro.Conversion} implementations to add to the compiler. @@ -391,6 +404,7 @@ private void doCompile(File sourceFileForModificationDetection, SpecificCompiler compiler.setGettersReturnOptional(gettersReturnOptional); compiler.setOptionalGettersForNullableFieldsOnly(optionalGettersForNullableFieldsOnly); compiler.setCreateSetters(createSetters); + compiler.setCreateNullSafeAnnotations(createNullSafeAnnotations); compiler.setEnableDecimalLogicalType(enableDecimalLogicalType); try { for (String customConversion : customConversions) { diff --git a/lang/java/tools/src/main/java/org/apache/avro/tool/SpecificCompilerTool.java b/lang/java/tools/src/main/java/org/apache/avro/tool/SpecificCompilerTool.java index 04eb5466bac..d82929e940f 100644 --- a/lang/java/tools/src/main/java/org/apache/avro/tool/SpecificCompilerTool.java +++ b/lang/java/tools/src/main/java/org/apache/avro/tool/SpecificCompilerTool.java @@ -50,7 +50,7 @@ public int run(InputStream in, PrintStream out, PrintStream err, List or if (origArgs.size() < 3) { System.err .println("Usage: [-encoding ] [-string] [-bigDecimal] [-fieldVisibility ] " - + "[-noSetters] [-addExtraOptionalGetters] [-optionalGetters ] " + + "[-noSetters] [-nullSafeAnnotations] [-addExtraOptionalGetters] [-optionalGetters ] " + "[-templateDir ] (schema|protocol) input... outputdir"); System.err.println(" input - input files or directories"); System.err.println(" outputdir - directory to write generated java"); @@ -58,6 +58,7 @@ public int run(InputStream in, PrintStream out, PrintStream err, List or System.err.println(" -string - use java.lang.String instead of Utf8"); System.err.println(" -fieldVisibility [private|public] - use either and default private"); System.err.println(" -noSetters - do not generate setters"); + System.err.println(" -nullSafeAnnotations - add @Nullable and @NotNull annotations"); System.err .println(" -addExtraOptionalGetters - generate extra getters with this format: 'getOptional'"); System.err.println( @@ -72,6 +73,7 @@ public int run(InputStream in, PrintStream out, PrintStream err, List or compilerOpts.stringType = StringType.CharSequence; compilerOpts.useLogicalDecimal = false; compilerOpts.createSetters = true; + compilerOpts.createNullSafeAnnotations = false; compilerOpts.optionalGettersType = Optional.empty(); compilerOpts.addExtraOptionalGetters = false; compilerOpts.encoding = Optional.empty(); @@ -85,6 +87,11 @@ public int run(InputStream in, PrintStream out, PrintStream err, List or args.remove(args.indexOf("-noSetters")); } + if (args.contains("-nullSafeAnnotations")) { + compilerOpts.createNullSafeAnnotations = true; + args.remove(args.indexOf("-nullSafeAnnotations")); + } + if (args.contains("-addExtraOptionalGetters")) { compilerOpts.addExtraOptionalGetters = true; args.remove(args.indexOf("-addExtraOptionalGetters")); @@ -172,6 +179,7 @@ private void executeCompiler(SpecificCompiler compiler, CompilerOptions opts, Fi throws IOException { compiler.setStringType(opts.stringType); compiler.setCreateSetters(opts.createSetters); + compiler.setCreateNullSafeAnnotations(opts.createNullSafeAnnotations); opts.optionalGettersType.ifPresent(choice -> { compiler.setGettersReturnOptional(true); @@ -267,6 +275,7 @@ private static class CompilerOptions { Optional fieldVisibility; boolean useLogicalDecimal; boolean createSetters; + boolean createNullSafeAnnotations; boolean addExtraOptionalGetters; Optional optionalGettersType; Optional templateDir; diff --git a/lang/java/tools/src/test/compiler/input/nullsafeannotationsfieldstest.avsc b/lang/java/tools/src/test/compiler/input/nullsafeannotationsfieldstest.avsc new file mode 100644 index 00000000000..04ef43e34ef --- /dev/null +++ b/lang/java/tools/src/test/compiler/input/nullsafeannotationsfieldstest.avsc @@ -0,0 +1,8 @@ +{"type":"record", "name":"NullSafeAnnotationsFieldsTest", "namespace": "avro.examples.baseball", "doc":"Test that @org.jetbrains.annotations.Nullable and @org.jetbrains.annotations.NotNull annotations are created for all fields", + "fields": [ + {"name": "name", "type": "string"}, + {"name": "nullable_name", "type": ["string", "null"]}, + {"name": "favorite_number", "type": "int"}, + {"name": "nullable_favorite_number", "type": ["int", "null"]} + ] +} diff --git a/lang/java/tools/src/test/compiler/output-string/avro/examples/baseball/NullSafeAnnotationsFieldsTest.java b/lang/java/tools/src/test/compiler/output-string/avro/examples/baseball/NullSafeAnnotationsFieldsTest.java new file mode 100644 index 00000000000..bf4d4fccc9c --- /dev/null +++ b/lang/java/tools/src/test/compiler/output-string/avro/examples/baseball/NullSafeAnnotationsFieldsTest.java @@ -0,0 +1,595 @@ +/** + * Autogenerated by Avro + * + * DO NOT EDIT DIRECTLY + */ +package avro.examples.baseball; + +import org.apache.avro.generic.GenericArray; +import org.apache.avro.specific.SpecificData; +import org.apache.avro.util.Utf8; +import org.apache.avro.message.BinaryMessageEncoder; +import org.apache.avro.message.BinaryMessageDecoder; +import org.apache.avro.message.SchemaStore; + +/** Test that @org.jetbrains.annotations.Nullable and @org.jetbrains.annotations.NotNull annotations are created for all fields */ +@org.apache.avro.specific.AvroGenerated +public class NullSafeAnnotationsFieldsTest extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord { + private static final long serialVersionUID = 2020521726426674816L; + + + public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"NullSafeAnnotationsFieldsTest\",\"namespace\":\"avro.examples.baseball\",\"doc\":\"Test that @org.jetbrains.annotations.Nullable and @org.jetbrains.annotations.NotNull annotations are created for all fields\",\"fields\":[{\"name\":\"name\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"nullable_name\",\"type\":[{\"type\":\"string\",\"avro.java.string\":\"String\"},\"null\"]},{\"name\":\"favorite_number\",\"type\":\"int\"},{\"name\":\"nullable_favorite_number\",\"type\":[\"int\",\"null\"]}]}"); + public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; } + + private static final SpecificData MODEL$ = new SpecificData(); + + private static final BinaryMessageEncoder ENCODER = + new BinaryMessageEncoder<>(MODEL$, SCHEMA$); + + private static final BinaryMessageDecoder DECODER = + new BinaryMessageDecoder<>(MODEL$, SCHEMA$); + + /** + * Return the BinaryMessageEncoder instance used by this class. + * @return the message encoder used by this class + */ + public static BinaryMessageEncoder getEncoder() { + return ENCODER; + } + + /** + * Return the BinaryMessageDecoder instance used by this class. + * @return the message decoder used by this class + */ + public static BinaryMessageDecoder getDecoder() { + return DECODER; + } + + /** + * Create a new BinaryMessageDecoder instance for this class that uses the specified {@link SchemaStore}. + * @param resolver a {@link SchemaStore} used to find schemas by fingerprint + * @return a BinaryMessageDecoder instance for this class backed by the given SchemaStore + */ + public static BinaryMessageDecoder createDecoder(SchemaStore resolver) { + return new BinaryMessageDecoder<>(MODEL$, SCHEMA$, resolver); + } + + /** + * Serializes this NullSafeAnnotationsFieldsTest to a ByteBuffer. + * @return a buffer holding the serialized data for this instance + * @throws java.io.IOException if this instance could not be serialized + */ + public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException { + return ENCODER.encode(this); + } + + /** + * Deserializes a NullSafeAnnotationsFieldsTest from a ByteBuffer. + * @param b a byte buffer holding serialized data for an instance of this class + * @return a NullSafeAnnotationsFieldsTest instance decoded from the given buffer + * @throws java.io.IOException if the given bytes could not be deserialized into an instance of this class + */ + public static NullSafeAnnotationsFieldsTest fromByteBuffer( + java.nio.ByteBuffer b) throws java.io.IOException { + return DECODER.decode(b); + } + + private java.lang.String name; + private java.lang.String nullable_name; + private int favorite_number; + private java.lang.Integer nullable_favorite_number; + + /** + * Default constructor. Note that this does not initialize fields + * to their default values from the schema. If that is desired then + * one should use newBuilder(). + */ + public NullSafeAnnotationsFieldsTest() {} + + /** + * All-args constructor. + * @param name The new value for name + * @param nullable_name The new value for nullable_name + * @param favorite_number The new value for favorite_number + * @param nullable_favorite_number The new value for nullable_favorite_number + */ + public NullSafeAnnotationsFieldsTest(@org.jetbrains.annotations.NotNull java.lang.String name, @org.jetbrains.annotations.Nullable java.lang.String nullable_name, @org.jetbrains.annotations.NotNull java.lang.Integer favorite_number, @org.jetbrains.annotations.Nullable java.lang.Integer nullable_favorite_number) { + this.name = name; + this.nullable_name = nullable_name; + this.favorite_number = favorite_number; + this.nullable_favorite_number = nullable_favorite_number; + } + + @Override + public org.apache.avro.specific.SpecificData getSpecificData() { return MODEL$; } + + @Override + public org.apache.avro.Schema getSchema() { return SCHEMA$; } + + // Used by DatumWriter. Applications should not call. + @Override + public java.lang.Object get(int field$) { + switch (field$) { + case 0: return name; + case 1: return nullable_name; + case 2: return favorite_number; + case 3: return nullable_favorite_number; + default: throw new IndexOutOfBoundsException("Invalid index: " + field$); + } + } + + // Used by DatumReader. Applications should not call. + @Override + @SuppressWarnings(value="unchecked") + public void put(int field$, java.lang.Object value$) { + switch (field$) { + case 0: name = value$ != null ? value$.toString() : null; break; + case 1: nullable_name = value$ != null ? value$.toString() : null; break; + case 2: favorite_number = (java.lang.Integer)value$; break; + case 3: nullable_favorite_number = (java.lang.Integer)value$; break; + default: throw new IndexOutOfBoundsException("Invalid index: " + field$); + } + } + + /** + * Gets the value of the 'name' field. + * @return The value of the 'name' field. + */ + @org.jetbrains.annotations.NotNull + public java.lang.String getName() { + return name; + } + + + /** + * Sets the value of the 'name' field. + * @param value the value to set. + */ + public void setName(@org.jetbrains.annotations.NotNull java.lang.String value) { + this.name = value; + } + + /** + * Gets the value of the 'nullable_name' field. + * @return The value of the 'nullable_name' field. + */ + @org.jetbrains.annotations.Nullable + public java.lang.String getNullableName() { + return nullable_name; + } + + + /** + * Sets the value of the 'nullable_name' field. + * @param value the value to set. + */ + public void setNullableName(@org.jetbrains.annotations.Nullable java.lang.String value) { + this.nullable_name = value; + } + + /** + * Gets the value of the 'favorite_number' field. + * @return The value of the 'favorite_number' field. + */ + @org.jetbrains.annotations.NotNull + public int getFavoriteNumber() { + return favorite_number; + } + + + /** + * Sets the value of the 'favorite_number' field. + * @param value the value to set. + */ + public void setFavoriteNumber(@org.jetbrains.annotations.NotNull int value) { + this.favorite_number = value; + } + + /** + * Gets the value of the 'nullable_favorite_number' field. + * @return The value of the 'nullable_favorite_number' field. + */ + @org.jetbrains.annotations.Nullable + public java.lang.Integer getNullableFavoriteNumber() { + return nullable_favorite_number; + } + + + /** + * Sets the value of the 'nullable_favorite_number' field. + * @param value the value to set. + */ + public void setNullableFavoriteNumber(@org.jetbrains.annotations.Nullable java.lang.Integer value) { + this.nullable_favorite_number = value; + } + + /** + * Creates a new NullSafeAnnotationsFieldsTest RecordBuilder. + * @return A new NullSafeAnnotationsFieldsTest RecordBuilder + */ + public static avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder newBuilder() { + return new avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder(); + } + + /** + * Creates a new NullSafeAnnotationsFieldsTest RecordBuilder by copying an existing Builder. + * @param other The existing builder to copy. + * @return A new NullSafeAnnotationsFieldsTest RecordBuilder + */ + public static avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder newBuilder(avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder other) { + if (other == null) { + return new avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder(); + } else { + return new avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder(other); + } + } + + /** + * Creates a new NullSafeAnnotationsFieldsTest RecordBuilder by copying an existing NullSafeAnnotationsFieldsTest instance. + * @param other The existing instance to copy. + * @return A new NullSafeAnnotationsFieldsTest RecordBuilder + */ + public static avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder newBuilder(avro.examples.baseball.NullSafeAnnotationsFieldsTest other) { + if (other == null) { + return new avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder(); + } else { + return new avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder(other); + } + } + + /** + * RecordBuilder for NullSafeAnnotationsFieldsTest instances. + */ + @org.apache.avro.specific.AvroGenerated + public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase + implements org.apache.avro.data.RecordBuilder { + + private java.lang.String name; + private java.lang.String nullable_name; + private int favorite_number; + private java.lang.Integer nullable_favorite_number; + + /** Creates a new Builder */ + private Builder() { + super(SCHEMA$, MODEL$); + } + + /** + * Creates a Builder by copying an existing Builder. + * @param other The existing Builder to copy. + */ + private Builder(avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder other) { + super(other); + if (isValidValue(fields()[0], other.name)) { + this.name = data().deepCopy(fields()[0].schema(), other.name); + fieldSetFlags()[0] = other.fieldSetFlags()[0]; + } + if (isValidValue(fields()[1], other.nullable_name)) { + this.nullable_name = data().deepCopy(fields()[1].schema(), other.nullable_name); + fieldSetFlags()[1] = other.fieldSetFlags()[1]; + } + if (isValidValue(fields()[2], other.favorite_number)) { + this.favorite_number = data().deepCopy(fields()[2].schema(), other.favorite_number); + fieldSetFlags()[2] = other.fieldSetFlags()[2]; + } + if (isValidValue(fields()[3], other.nullable_favorite_number)) { + this.nullable_favorite_number = data().deepCopy(fields()[3].schema(), other.nullable_favorite_number); + fieldSetFlags()[3] = other.fieldSetFlags()[3]; + } + } + + /** + * Creates a Builder by copying an existing NullSafeAnnotationsFieldsTest instance + * @param other The existing instance to copy. + */ + private Builder(avro.examples.baseball.NullSafeAnnotationsFieldsTest other) { + super(SCHEMA$, MODEL$); + if (isValidValue(fields()[0], other.name)) { + this.name = data().deepCopy(fields()[0].schema(), other.name); + fieldSetFlags()[0] = true; + } + if (isValidValue(fields()[1], other.nullable_name)) { + this.nullable_name = data().deepCopy(fields()[1].schema(), other.nullable_name); + fieldSetFlags()[1] = true; + } + if (isValidValue(fields()[2], other.favorite_number)) { + this.favorite_number = data().deepCopy(fields()[2].schema(), other.favorite_number); + fieldSetFlags()[2] = true; + } + if (isValidValue(fields()[3], other.nullable_favorite_number)) { + this.nullable_favorite_number = data().deepCopy(fields()[3].schema(), other.nullable_favorite_number); + fieldSetFlags()[3] = true; + } + } + + /** + * Gets the value of the 'name' field. + * @return The value. + */ + public java.lang.String getName() { + return name; + } + + + /** + * Sets the value of the 'name' field. + * @param value The value of 'name'. + * @return This builder. + */ + public avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder setName(@org.jetbrains.annotations.NotNull java.lang.String value) { + validate(fields()[0], value); + this.name = value; + fieldSetFlags()[0] = true; + return this; + } + + /** + * Checks whether the 'name' field has been set. + * @return True if the 'name' field has been set, false otherwise. + */ + public boolean hasName() { + return fieldSetFlags()[0]; + } + + + /** + * Clears the value of the 'name' field. + * @return This builder. + */ + public avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder clearName() { + name = null; + fieldSetFlags()[0] = false; + return this; + } + + /** + * Gets the value of the 'nullable_name' field. + * @return The value. + */ + public java.lang.String getNullableName() { + return nullable_name; + } + + + /** + * Sets the value of the 'nullable_name' field. + * @param value The value of 'nullable_name'. + * @return This builder. + */ + public avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder setNullableName(@org.jetbrains.annotations.Nullable java.lang.String value) { + validate(fields()[1], value); + this.nullable_name = value; + fieldSetFlags()[1] = true; + return this; + } + + /** + * Checks whether the 'nullable_name' field has been set. + * @return True if the 'nullable_name' field has been set, false otherwise. + */ + public boolean hasNullableName() { + return fieldSetFlags()[1]; + } + + + /** + * Clears the value of the 'nullable_name' field. + * @return This builder. + */ + public avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder clearNullableName() { + nullable_name = null; + fieldSetFlags()[1] = false; + return this; + } + + /** + * Gets the value of the 'favorite_number' field. + * @return The value. + */ + public int getFavoriteNumber() { + return favorite_number; + } + + + /** + * Sets the value of the 'favorite_number' field. + * @param value The value of 'favorite_number'. + * @return This builder. + */ + public avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder setFavoriteNumber(@org.jetbrains.annotations.NotNull int value) { + validate(fields()[2], value); + this.favorite_number = value; + fieldSetFlags()[2] = true; + return this; + } + + /** + * Checks whether the 'favorite_number' field has been set. + * @return True if the 'favorite_number' field has been set, false otherwise. + */ + public boolean hasFavoriteNumber() { + return fieldSetFlags()[2]; + } + + + /** + * Clears the value of the 'favorite_number' field. + * @return This builder. + */ + public avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder clearFavoriteNumber() { + fieldSetFlags()[2] = false; + return this; + } + + /** + * Gets the value of the 'nullable_favorite_number' field. + * @return The value. + */ + public java.lang.Integer getNullableFavoriteNumber() { + return nullable_favorite_number; + } + + + /** + * Sets the value of the 'nullable_favorite_number' field. + * @param value The value of 'nullable_favorite_number'. + * @return This builder. + */ + public avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder setNullableFavoriteNumber(@org.jetbrains.annotations.Nullable java.lang.Integer value) { + validate(fields()[3], value); + this.nullable_favorite_number = value; + fieldSetFlags()[3] = true; + return this; + } + + /** + * Checks whether the 'nullable_favorite_number' field has been set. + * @return True if the 'nullable_favorite_number' field has been set, false otherwise. + */ + public boolean hasNullableFavoriteNumber() { + return fieldSetFlags()[3]; + } + + + /** + * Clears the value of the 'nullable_favorite_number' field. + * @return This builder. + */ + public avro.examples.baseball.NullSafeAnnotationsFieldsTest.Builder clearNullableFavoriteNumber() { + nullable_favorite_number = null; + fieldSetFlags()[3] = false; + return this; + } + + @Override + @SuppressWarnings("unchecked") + public NullSafeAnnotationsFieldsTest build() { + try { + NullSafeAnnotationsFieldsTest record = new NullSafeAnnotationsFieldsTest(); + record.name = fieldSetFlags()[0] ? this.name : (java.lang.String) defaultValue(fields()[0]); + record.nullable_name = fieldSetFlags()[1] ? this.nullable_name : (java.lang.String) defaultValue(fields()[1]); + record.favorite_number = fieldSetFlags()[2] ? this.favorite_number : (java.lang.Integer) defaultValue(fields()[2]); + record.nullable_favorite_number = fieldSetFlags()[3] ? this.nullable_favorite_number : (java.lang.Integer) defaultValue(fields()[3]); + return record; + } catch (org.apache.avro.AvroMissingFieldException e) { + throw e; + } catch (java.lang.Exception e) { + throw new org.apache.avro.AvroRuntimeException(e); + } + } + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumWriter + WRITER$ = (org.apache.avro.io.DatumWriter)MODEL$.createDatumWriter(SCHEMA$); + + @Override public void writeExternal(java.io.ObjectOutput out) + throws java.io.IOException { + WRITER$.write(this, SpecificData.getEncoder(out)); + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumReader + READER$ = (org.apache.avro.io.DatumReader)MODEL$.createDatumReader(SCHEMA$); + + @Override public void readExternal(java.io.ObjectInput in) + throws java.io.IOException { + READER$.read(this, SpecificData.getDecoder(in)); + } + + @Override protected boolean hasCustomCoders() { return true; } + + @Override public void customEncode(org.apache.avro.io.Encoder out) + throws java.io.IOException + { + out.writeString(this.name); + + if (this.nullable_name == null) { + out.writeIndex(1); + out.writeNull(); + } else { + out.writeIndex(0); + out.writeString(this.nullable_name); + } + + out.writeInt(this.favorite_number); + + if (this.nullable_favorite_number == null) { + out.writeIndex(1); + out.writeNull(); + } else { + out.writeIndex(0); + out.writeInt(this.nullable_favorite_number); + } + + } + + @Override public void customDecode(org.apache.avro.io.ResolvingDecoder in) + throws java.io.IOException + { + org.apache.avro.Schema.Field[] fieldOrder = in.readFieldOrderIfDiff(); + if (fieldOrder == null) { + this.name = in.readString(); + + if (in.readIndex() != 0) { + in.readNull(); + this.nullable_name = null; + } else { + this.nullable_name = in.readString(); + } + + this.favorite_number = in.readInt(); + + if (in.readIndex() != 0) { + in.readNull(); + this.nullable_favorite_number = null; + } else { + this.nullable_favorite_number = in.readInt(); + } + + } else { + for (int i = 0; i < 4; i++) { + switch (fieldOrder[i].pos()) { + case 0: + this.name = in.readString(); + break; + + case 1: + if (in.readIndex() != 0) { + in.readNull(); + this.nullable_name = null; + } else { + this.nullable_name = in.readString(); + } + break; + + case 2: + this.favorite_number = in.readInt(); + break; + + case 3: + if (in.readIndex() != 0) { + in.readNull(); + this.nullable_favorite_number = null; + } else { + this.nullable_favorite_number = in.readInt(); + } + break; + + default: + throw new java.io.IOException("Corrupt ResolvingDecoder."); + } + } + } + } +} + + + + + + + + + + diff --git a/lang/java/tools/src/test/java/org/apache/avro/tool/TestSpecificCompilerTool.java b/lang/java/tools/src/test/java/org/apache/avro/tool/TestSpecificCompilerTool.java index bb66545dbb0..03768fea4b6 100644 --- a/lang/java/tools/src/test/java/org/apache/avro/tool/TestSpecificCompilerTool.java +++ b/lang/java/tools/src/test/java/org/apache/avro/tool/TestSpecificCompilerTool.java @@ -58,6 +58,8 @@ public class TestSpecificCompilerTool { "avro/examples/baseball/Position.java"); private static final File TEST_EXPECTED_STRING_PLAYER = new File(TEST_EXPECTED_STRING_OUTPUT_DIR, "avro/examples/baseball/Player.java"); + private static final File TEST_EXPECTED_STRING_NULL_SAFE_ANNOTATIONS_TEST = new File(TEST_EXPECTED_STRING_OUTPUT_DIR, + "avro/examples/baseball/NullSafeAnnotationsFieldsTest.java"); private static final File TEST_EXPECTED_STRING_FIELDTEST = new File(TEST_EXPECTED_STRING_OUTPUT_DIR, "avro/examples/baseball/FieldTest.java"); private static final File TEST_EXPECTED_STRING_PROTO = new File(TEST_EXPECTED_STRING_OUTPUT_DIR, @@ -83,6 +85,8 @@ public class TestSpecificCompilerTool { "avro/examples/baseball/Position.java"); private static final File TEST_OUTPUT_STRING_FIELDTEST = new File(TEST_OUTPUT_STRING_DIR, "avro/examples/baseball/FieldTest.java"); + private static final File TEST_OUTPUT_STRING_NULL_SAFE_ANNOTATIONS_TEST = new File(TEST_OUTPUT_STRING_DIR, + "avro/examples/baseball/NullSafeAnnotationsFieldsTest.java"); private static final File TEST_OUTPUT_STRING_PROTO = new File(TEST_OUTPUT_STRING_DIR, "avro/examples/baseball/Proto.java"); @@ -109,6 +113,15 @@ void compileSchemaWithOptionalGettersForNullableFieldsOnly() throws Exception { assertFileMatch(TEST_EXPECTED_OPTIONAL_GETTERS_FOR_NULLABLE_FIELDS, TEST_OUTPUT_OPTIONAL_GETTERS_NULLABLE_FIELDS); } + @Test + void compileSchemaWithNullSafeAnnotationsFields() throws Exception { + + TEST_OUTPUT_STRING_NULL_SAFE_ANNOTATIONS_TEST.delete(); + doCompile(new String[] { "-encoding", "UTF-8", "-nullSafeAnnotations", "-string", "schema", + TEST_INPUT_DIR.toString() + "/nullsafeannotationsfieldstest.avsc", TEST_OUTPUT_STRING_DIR.getPath() }); + assertFileMatch(TEST_EXPECTED_STRING_NULL_SAFE_ANNOTATIONS_TEST, TEST_OUTPUT_STRING_NULL_SAFE_ANNOTATIONS_TEST); + } + @Test void compileSchemaWithOptionalGettersForAllFields() throws Exception { diff --git a/pom.xml b/pom.xml index 6868dacf780..f70a664e9ed 100644 --- a/pom.xml +++ b/pom.xml @@ -396,6 +396,7 @@ lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithoutLogicalTypes.java lang/java/ipc-netty/src/test/resources/**/*.txt + lang/java/tools/src/test/compiler/output-string/avro/examples/baseball/NullSafeAnnotationsFieldsTest.java lang/java/tools/src/test/compiler/output-string/avro/examples/baseball/Player.java lang/java/tools/src/test/compiler/output-string/avro/examples/baseball/Position.java lang/java/tools/src/test/compiler/output-string/avro/examples/baseball/FieldTest.java