Skip to content

Commit a296c4d

Browse files
AVRO-4119: [java] Make Nullable and NotNull annotations configurable (apache#3312)
Add nullSafeAnnotationNullable and nullSafeAnnotationNotNull configuration options to allow specifying the exact annotations to use when createNullSafeAnnotations is enabled. This allows using annotations besides the JetBrains annotations (which remain the default for backward-compatibility). Co-authored-by: Oscar Westra van Holthe - Kind <opwvhk@apache.org>
1 parent a39d317 commit a296c4d

11 files changed

Lines changed: 784 additions & 77 deletions

File tree

lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ void addLogicalTypeConversions(SpecificData specificData) {
131131
private String suffix = ".java";
132132
private List<Object> additionalVelocityTools = Collections.emptyList();
133133

134+
private String nullSafeAnnotationNullable = "org.jetbrains.annotations.Nullable";
135+
private String nullSafeAnnotationNotNull = "org.jetbrains.annotations.NotNull";
136+
134137
private String recordSpecificClass = "org.apache.avro.specific.SpecificRecordBase";
135138

136139
private String errorSpecificClass = "org.apache.avro.specific.SpecificExceptionBase";
@@ -244,12 +247,42 @@ public boolean isCreateNullSafeAnnotations() {
244247
}
245248

246249
/**
247-
* Set to true to add jetbrains @Nullable and @NotNull annotations
250+
* Set to true to add @Nullable and @NotNull annotations. By default, JetBrains
251+
* annotations are used (org.jetbrains.annotations.Nullable and
252+
* org.jetbrains.annotations.NotNull) but this can be overridden using
253+
* {@link #setNullSafeAnnotationNullable)} and
254+
* {@link #setNullSafeAnnotationNotNull)}.
248255
*/
249256
public void setCreateNullSafeAnnotations(boolean createNullSafeAnnotations) {
250257
this.createNullSafeAnnotations = createNullSafeAnnotations;
251258
}
252259

260+
public String getNullSafeAnnotationNullable() {
261+
return this.nullSafeAnnotationNullable;
262+
}
263+
264+
/**
265+
* Sets the annotation to use for nullable fields. Default is
266+
* "org.jetbrains.annotations.Nullable". The annotation must include the full
267+
* package path.
268+
*/
269+
public void setNullSafeAnnotationNullable(String nullSafeAnnotationNullable) {
270+
this.nullSafeAnnotationNullable = nullSafeAnnotationNullable;
271+
}
272+
273+
public String getNullSafeAnnotationNotNull() {
274+
return this.nullSafeAnnotationNotNull;
275+
}
276+
277+
/**
278+
* Sets the annotation to use for non-nullable fields. Default is
279+
* "org.jetbrains.annotations.NotNull". The annotation must include the full
280+
* package path.
281+
*/
282+
public void setNullSafeAnnotationNotNull(String nullSafeAnnotationNotNull) {
283+
this.nullSafeAnnotationNotNull = nullSafeAnnotationNotNull;
284+
}
285+
253286
public boolean isCreateOptionalGetters() {
254287
return this.createOptionalGetters;
255288
}

lang/java/compiler/src/main/velocity/org/apache/avro/compiler/specific/templates/java/classic/record.vm

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public class ${this.mangleTypeIdentifier($schema.getName())} extends ${this.getS
160160
#end
161161
#end
162162
*/
163-
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) {
163+
public ${this.mangleTypeIdentifier($schema.getName())}(#foreach($field in $schema.getFields())#if(${this.createNullSafeAnnotations})#if(${field.schema().isNullable()})@${this.nullSafeAnnotationNullable}#else@${this.nullSafeAnnotationNotNull}#end #end${this.javaType($field.schema())} ${this.mangle($field.name())}#if($foreach.count < $schema.getFields().size()), #end#end) {
164164
#foreach ($field in $schema.getFields())
165165
${this.generateSetterCode($field.schema(), ${this.mangle($field.name())}, ${this.mangle($field.name())})}
166166
#end
@@ -244,9 +244,9 @@ public class ${this.mangleTypeIdentifier($schema.getName())} extends ${this.getS
244244
*/
245245
#if(${this.createNullSafeAnnotations})
246246
#if (${field.schema().isNullable()})
247-
@org.jetbrains.annotations.Nullable
247+
@${this.nullSafeAnnotationNullable}
248248
#else
249-
@org.jetbrains.annotations.NotNull
249+
@${this.nullSafeAnnotationNotNull}
250250
#end
251251
#end
252252
public ${this.javaUnbox($field.schema(), false)} ${this.generateGetMethod($schema, $field)}() {
@@ -273,7 +273,7 @@ public class ${this.mangleTypeIdentifier($schema.getName())} extends ${this.getS
273273
#end
274274
* @param value the value to set.
275275
*/
276-
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) {
276+
public void ${this.generateSetMethod($schema, $field)}(#if(${this.createNullSafeAnnotations})#if(${field.schema().isNullable()})@${this.nullSafeAnnotationNullable}#else@${this.nullSafeAnnotationNotNull}#end #end${this.javaUnbox($field.schema(), false)} value) {
277277
${this.generateSetterCode($field.schema(), ${this.mangle($field.name(), $schema.isError())}, "value")}
278278
}
279279
#end
@@ -429,7 +429,7 @@ public class ${this.mangleTypeIdentifier($schema.getName())} extends ${this.getS
429429
* @param value The value of '${this.mangle($field.name(), $schema.isError())}'.
430430
* @return This builder.
431431
*/
432-
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) {
432+
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()})@${this.nullSafeAnnotationNullable}#else@${this.nullSafeAnnotationNotNull}#end #end${this.javaUnbox($field.schema(), false)} value) {
433433
validate(fields()[$field.pos()], value);
434434
#if (${this.hasBuilder($field.schema())})
435435
this.${this.mangle($field.name(), $schema.isError())}Builder = null;

lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,10 @@ public abstract class AbstractAvroMojo extends AbstractMojo {
183183
protected boolean createSetters;
184184

185185
/**
186-
* The createNullSafeAnnotations parameter adds JetBrains {@literal @}Nullable
187-
* and {@literal @}NotNull annotations for fhe fields of the record. The default
188-
* is to not include annotations.
186+
* If set to true, {@literal @}Nullable and {@literal @}NotNull annotations are
187+
* added to fields of the record. The default is false. If enabled, JetBrains
188+
* annotations are used by default but other annotations can be specified via
189+
* the nullSafeAnnotationNullable and nullSafeAnnotationNotNull parameters.
189190
*
190191
* @parameter property="createNullSafeAnnotations"
191192
*
@@ -195,6 +196,32 @@ public abstract class AbstractAvroMojo extends AbstractMojo {
195196
*/
196197
protected boolean createNullSafeAnnotations = false;
197198

199+
/**
200+
* Controls which annotation should be added to nullable fields if
201+
* createNullSafeAnnotations is enabled. The default is
202+
* org.jetbrains.annotations.Nullable.
203+
*
204+
* @parameter property="nullSafeAnnotationNullable"
205+
*
206+
* @see <a href=
207+
* "https://www.jetbrains.com/help/idea/annotating-source-code.html#nullability-annotations">
208+
* JetBrains nullability annotations</a>
209+
*/
210+
protected String nullSafeAnnotationNullable = "org.jetbrains.annotations.Nullable";
211+
212+
/**
213+
* Controls which annotation should be added to non-nullable fields if
214+
* createNullSafeAnnotations is enabled. The default is
215+
* org.jetbrains.annotations.NotNull.
216+
*
217+
* @parameter property="nullSafeAnnotationNotNull"
218+
*
219+
* @see <a href=
220+
* "https://www.jetbrains.com/help/idea/annotating-source-code.html#nullability-annotations">
221+
* JetBrains nullability annotations</a>
222+
*/
223+
protected String nullSafeAnnotationNotNull = "org.jetbrains.annotations.NotNull";
224+
198225
/**
199226
* A set of fully qualified class names of custom
200227
* {@link org.apache.avro.Conversion} implementations to add to the compiler.
@@ -405,6 +432,8 @@ private void doCompile(File sourceFileForModificationDetection, SpecificCompiler
405432
compiler.setOptionalGettersForNullableFieldsOnly(optionalGettersForNullableFieldsOnly);
406433
compiler.setCreateSetters(createSetters);
407434
compiler.setCreateNullSafeAnnotations(createNullSafeAnnotations);
435+
compiler.setNullSafeAnnotationNullable(nullSafeAnnotationNullable);
436+
compiler.setNullSafeAnnotationNotNull(nullSafeAnnotationNotNull);
408437
compiler.setEnableDecimalLogicalType(enableDecimalLogicalType);
409438
try {
410439
for (String customConversion : customConversions) {

lang/java/tools/src/main/java/org/apache/avro/tool/SpecificCompilerTool.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,20 @@ public int run(InputStream in, PrintStream out, PrintStream err, List<String> or
5050
if (origArgs.size() < 3) {
5151
System.err
5252
.println("Usage: [-encoding <outputencoding>] [-string] [-bigDecimal] [-fieldVisibility <visibilityType>] "
53-
+ "[-noSetters] [-nullSafeAnnotations] [-addExtraOptionalGetters] [-optionalGetters <optionalGettersType>] "
54-
+ "[-templateDir <templateDir>] (schema|protocol) input... outputdir");
53+
+ "[-noSetters] [-nullSafeAnnotations] [-nullSafeAnnotationNullable <nullableAnnotation>] "
54+
+ "[-nullSafeAnnotationNotNull <notNullAnnotation>] [-addExtraOptionalGetters] "
55+
+ "[-optionalGetters <optionalGettersType>] [-templateDir <templateDir>] "
56+
+ "(schema|protocol) input... outputdir");
5557
System.err.println(" input - input files or directories");
5658
System.err.println(" outputdir - directory to write generated java");
5759
System.err.println(" -encoding <outputencoding> - set the encoding of " + "output file(s)");
5860
System.err.println(" -string - use java.lang.String instead of Utf8");
5961
System.err.println(" -fieldVisibility [private|public] - use either and default private");
6062
System.err.println(" -noSetters - do not generate setters");
6163
System.err.println(" -nullSafeAnnotations - add @Nullable and @NotNull annotations");
64+
System.err.println(" -nullSafeAnnotationNullable - full package path of annotation to use for nullable fields");
65+
System.err
66+
.println(" -nullSafeAnnotationNotNull - full package path of annotation to use for non-nullable fields");
6267
System.err
6368
.println(" -addExtraOptionalGetters - generate extra getters with this format: 'getOptional<FieldName>'");
6469
System.err.println(
@@ -74,13 +79,16 @@ public int run(InputStream in, PrintStream out, PrintStream err, List<String> or
7479
compilerOpts.useLogicalDecimal = false;
7580
compilerOpts.createSetters = true;
7681
compilerOpts.createNullSafeAnnotations = false;
82+
compilerOpts.nullSafeAnnotationNullable = Optional.empty();
83+
compilerOpts.nullSafeAnnotationNotNull = Optional.empty();
7784
compilerOpts.optionalGettersType = Optional.empty();
7885
compilerOpts.addExtraOptionalGetters = false;
7986
compilerOpts.encoding = Optional.empty();
8087
compilerOpts.templateDir = Optional.empty();
8188
compilerOpts.fieldVisibility = Optional.empty();
8289

8390
List<String> args = new ArrayList<>(origArgs);
91+
int arg = 0;
8492

8593
if (args.contains("-noSetters")) {
8694
compilerOpts.createSetters = false;
@@ -92,11 +100,24 @@ public int run(InputStream in, PrintStream out, PrintStream err, List<String> or
92100
args.remove(args.indexOf("-nullSafeAnnotations"));
93101
}
94102

103+
if (args.contains("-nullSafeAnnotationNullable")) {
104+
arg = args.indexOf("-nullSafeAnnotationNullable") + 1;
105+
compilerOpts.nullSafeAnnotationNullable = Optional.of(args.get(arg));
106+
args.remove(arg);
107+
args.remove(arg - 1);
108+
}
109+
110+
if (args.contains("-nullSafeAnnotationNotNull")) {
111+
arg = args.indexOf("-nullSafeAnnotationNotNull") + 1;
112+
compilerOpts.nullSafeAnnotationNotNull = Optional.of(args.get(arg));
113+
args.remove(arg);
114+
args.remove(arg - 1);
115+
}
116+
95117
if (args.contains("-addExtraOptionalGetters")) {
96118
compilerOpts.addExtraOptionalGetters = true;
97119
args.remove(args.indexOf("-addExtraOptionalGetters"));
98120
}
99-
int arg = 0;
100121

101122
if (args.contains("-optionalGetters")) {
102123
arg = args.indexOf("-optionalGetters") + 1;
@@ -180,6 +201,8 @@ private void executeCompiler(SpecificCompiler compiler, CompilerOptions opts, Fi
180201
compiler.setStringType(opts.stringType);
181202
compiler.setCreateSetters(opts.createSetters);
182203
compiler.setCreateNullSafeAnnotations(opts.createNullSafeAnnotations);
204+
opts.nullSafeAnnotationNullable.ifPresent(compiler::setNullSafeAnnotationNullable);
205+
opts.nullSafeAnnotationNotNull.ifPresent(compiler::setNullSafeAnnotationNotNull);
183206

184207
opts.optionalGettersType.ifPresent(choice -> {
185208
compiler.setGettersReturnOptional(true);
@@ -276,6 +299,8 @@ private static class CompilerOptions {
276299
boolean useLogicalDecimal;
277300
boolean createSetters;
278301
boolean createNullSafeAnnotations;
302+
Optional<String> nullSafeAnnotationNullable;
303+
Optional<String> nullSafeAnnotationNotNull;
279304
boolean addExtraOptionalGetters;
280305
Optional<OptionalGettersType> optionalGettersType;
281306
Optional<String> templateDir;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{"type":"record", "name":"JetBrainsNullSafeAnnotationsFieldsTest", "namespace": "avro.examples.baseball", "doc":"Test that @org.jetbrains.annotations.Nullable and @org.jetbrains.annotations.NotNull annotations are created for all fields",
2+
"fields": [
3+
{"name": "name", "type": "string"},
4+
{"name": "nullable_name", "type": ["string", "null"]},
5+
{"name": "favorite_number", "type": "int"},
6+
{"name": "nullable_favorite_number", "type": ["int", "null"]}
7+
]
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{"type":"record", "name":"JSpecifyNullSafeAnnotationsFieldsTest", "namespace": "avro.examples.baseball", "doc":"Test that @org.jspecify.annotations.Nullable and @org.jspecify.annotations.NonNull annotations are created for all fields",
2+
"fields": [
3+
{"name": "name", "type": "string"},
4+
{"name": "nullable_name", "type": ["string", "null"]},
5+
{"name": "favorite_number", "type": "int"},
6+
{"name": "nullable_favorite_number", "type": ["int", "null"]}
7+
]
8+
}

lang/java/tools/src/test/compiler/input/nullsafeannotationsfieldstest.avsc

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)