Skip to content

Commit 4146ba8

Browse files
committed
Add support for Jakarta Nullability Annotations.
1 parent d1741a1 commit 4146ba8

File tree

6 files changed

+171
-6
lines changed

6 files changed

+171
-6
lines changed

pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<scala>2.11.7</scala>
3434
<xmlbeam>1.4.24</xmlbeam>
3535
<java-module-name>spring.data.commons</java-module-name>
36+
<jakarta-annotation-api>2.1.1</jakarta-annotation-api>
3637
<kotlin.api.target>1.8</kotlin.api.target>
3738
</properties>
3839

@@ -41,40 +42,48 @@
4142
<groupId>org.springframework</groupId>
4243
<artifactId>spring-core</artifactId>
4344
</dependency>
45+
4446
<dependency>
4547
<groupId>org.springframework</groupId>
4648
<artifactId>spring-beans</artifactId>
4749
</dependency>
50+
4851
<dependency>
4952
<groupId>org.springframework</groupId>
5053
<artifactId>spring-context</artifactId>
5154
<optional>true</optional>
5255
</dependency>
56+
5357
<dependency>
5458
<groupId>org.springframework</groupId>
5559
<artifactId>spring-expression</artifactId>
5660
<optional>true</optional>
5761
</dependency>
62+
5863
<dependency>
5964
<groupId>org.springframework</groupId>
6065
<artifactId>spring-tx</artifactId>
6166
<optional>true</optional>
6267
</dependency>
68+
6369
<dependency>
6470
<groupId>org.springframework</groupId>
6571
<artifactId>spring-oxm</artifactId>
6672
<optional>true</optional>
6773
</dependency>
74+
6875
<dependency>
6976
<groupId>com.fasterxml.jackson.core</groupId>
7077
<artifactId>jackson-databind</artifactId>
7178
<optional>true</optional>
7279
</dependency>
80+
7381
<dependency>
7482
<groupId>org.springframework</groupId>
7583
<artifactId>spring-web</artifactId>
7684
<optional>true</optional>
7785
</dependency>
86+
7887
<dependency>
7988
<groupId>org.springframework</groupId>
8089
<artifactId>spring-webflux</artifactId>
@@ -92,12 +101,14 @@
92101
<artifactId>jakarta.servlet-api</artifactId>
93102
<scope>provided</scope>
94103
</dependency>
104+
95105
<dependency>
96106
<groupId>jakarta.xml.bind</groupId>
97107
<artifactId>jakarta.xml.bind-api</artifactId>
98108
<version>${jaxb}</version>
99109
<scope>provided</scope>
100110
</dependency>
111+
101112
<dependency>
102113
<groupId>jakarta.annotation</groupId>
103114
<artifactId>jakarta.annotation-api</artifactId>

src/main/java/org/springframework/data/util/NullabilityIntrospector.java

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import java.lang.annotation.Annotation;
1919
import java.lang.annotation.ElementType;
2020
import java.lang.reflect.AnnotatedElement;
21-
import java.lang.reflect.Field;
2221
import java.lang.reflect.Method;
2322
import java.lang.reflect.Parameter;
2423
import java.util.ArrayList;
@@ -53,6 +52,10 @@ class NullabilityIntrospector implements Nullability.Introspector {
5352
providers.add(new Jsr305Provider());
5453
}
5554

55+
if (JakartaAnnotation.isAvailable()) {
56+
providers.add(new JakartaAnnotation());
57+
}
58+
5659
providers.add(new SpringProvider());
5760
}
5861

@@ -270,9 +273,59 @@ static boolean isNullable(Annotation annotation) {
270273
}
271274
}
272275

273-
private static ElementType getElementType(AnnotatedElement element) {
274-
return element instanceof Method ? ElementType.METHOD
275-
: element instanceof Field ? ElementType.FIELD : ElementType.PARAMETER;
276+
/**
277+
* Provider based on the JSR-305 (dormant) spec. Elements can be either annotated with
278+
* {@code @Nonnull}/{@code @Nullable} directly or through meta-annotations that are composed of
279+
* {@code @Nonnull}/{@code @Nullable} and {@code @TypeQualifierDefault}.
280+
*/
281+
static class JakartaAnnotation extends NullabilityProvider {
282+
283+
private static final Class<Annotation> NON_NULL = findClass("jakarta.annotation.Nonnull");
284+
private static final Class<Annotation> NULLABLE = findClass("jakarta.annotation.Nullable");
285+
286+
public static boolean isAvailable() {
287+
return NON_NULL != null && NULLABLE != null;
288+
}
289+
290+
@Override
291+
Spec evaluate(AnnotatedElement element, ElementType elementType) {
292+
293+
if (element.isAnnotationPresent(NULLABLE) || MergedAnnotations.from(element).isPresent(NULLABLE)) {
294+
return Spec.NULLABLE;
295+
}
296+
297+
Annotation[] annotations = element.getAnnotations();
298+
299+
for (Annotation annotation : annotations) {
300+
301+
if (test(NON_NULL, annotation)) {
302+
return Spec.NON_NULL;
303+
}
304+
305+
if (test(NULLABLE, annotation)) {
306+
return Spec.NULLABLE;
307+
}
308+
}
309+
310+
return Spec.UNSPECIFIED;
311+
}
312+
313+
private static boolean test(Class<Annotation> annotationClass, Annotation annotation) {
314+
315+
if (annotation.annotationType().equals(annotationClass)) {
316+
return true;
317+
}
318+
319+
MergedAnnotations annotations = MergedAnnotations.from(annotation.annotationType());
320+
if (annotations.isPresent(annotationClass)) {
321+
Annotation meta = annotations.get(annotationClass).synthesize();
322+
323+
return true;
324+
}
325+
326+
return false;
327+
}
328+
276329
}
277330

278331
@Nullable

src/test/java/org/springframework/data/util/NullableUtilsUnitTests.java

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,28 @@ void packageAnnotatedShouldConsiderNonNullAnnotationForMethod() {
7171
assertThat(NullableUtils.isNonNull(NonNullOnPackage.class, ElementType.PACKAGE)).isFalse();
7272
}
7373

74+
@Test //
75+
void shouldConsiderJakartaNonNullParameters() {
76+
77+
var method = ReflectionUtils.findMethod(org.springframework.data.util.nonnull.jakarta.NonNullOnPackage.class, "someMethod", String.class, String.class);
78+
Nullability.Introspector introspector = Nullability.introspect(method.getDeclaringClass());
79+
Nullability mrt = introspector.forReturnType(method);
80+
81+
assertThat(mrt.isDeclared()).isTrue();
82+
assertThat(mrt.isNonNull()).isTrue();
83+
assertThat(mrt.isNullable()).isFalse();
84+
85+
Nullability pn0 = introspector.forParameter(MethodParameter.forExecutable(method, 0));
86+
assertThat(pn0.isDeclared()).isTrue();
87+
assertThat(pn0.isNullable()).isFalse();
88+
assertThat(pn0.isNonNull()).isTrue();
89+
90+
Nullability pn1 = introspector.forParameter(MethodParameter.forExecutable(method, 1));
91+
assertThat(pn1.isDeclared()).isTrue();
92+
assertThat(pn1.isNullable()).isTrue();
93+
assertThat(pn1.isNonNull()).isFalse();
94+
}
95+
7496
@Test // DATACMNS-1154
7597
void shouldConsiderJsr305NonNullParameters() {
7698

@@ -158,7 +180,7 @@ void shouldConsiderParametersNullableAnnotation() {
158180
}
159181

160182
@Test // DATACMNS-1154
161-
void shouldConsiderParametersJsr305NullableMetaAnnotation() {
183+
void shouldConsiderMethodReturnJsr305NullableMetaAnnotation() {
162184

163185
var method = ReflectionUtils.findMethod(NullableAnnotatedType.class, "jsr305NullableReturn");
164186

@@ -172,7 +194,7 @@ void shouldConsiderParametersJsr305NullableMetaAnnotation() {
172194
}
173195

174196
@Test // DATACMNS-1154
175-
void shouldConsiderParametersJsr305NonnullAnnotation() {
197+
void shouldConsiderMethodReturnJsr305NonnullAnnotation() {
176198

177199
var method = ReflectionUtils.findMethod(NullableAnnotatedType.class, "jsr305NullableReturnWhen");
178200

@@ -184,4 +206,28 @@ void shouldConsiderParametersJsr305NonnullAnnotation() {
184206
assertThat(mrt.isNullable()).isTrue();
185207
assertThat(mrt.isNonNull()).isFalse();
186208
}
209+
210+
@Test //
211+
void shouldConsiderMethodReturnJakartaNonnullAnnotation() {
212+
213+
var method = ReflectionUtils.findMethod(NullableAnnotatedType.class, "jakartaNonnullReturnWhen");
214+
215+
Nullability mrt = Nullability.forMethodReturnType(method);
216+
217+
assertThat(mrt.isDeclared()).isTrue();
218+
assertThat(mrt.isNullable()).isFalse();
219+
assertThat(mrt.isNonNull()).isTrue();
220+
}
221+
222+
@Test //
223+
void shouldConsiderMethodReturnJakartaNullableAnnotation() {
224+
225+
var method = ReflectionUtils.findMethod(NullableAnnotatedType.class, "jakartaNullableReturnWhen");
226+
227+
Nullability mrt = Nullability.forMethodReturnType(method);
228+
229+
assertThat(mrt.isDeclared()).isTrue();
230+
assertThat(mrt.isNullable()).isTrue();
231+
assertThat(mrt.isNonNull()).isFalse();
232+
}
187233
}

src/test/java/org/springframework/data/util/nonnull/NullableAnnotatedType.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,10 @@ public interface NullableAnnotatedType {
3434

3535
@javax.annotation.Nonnull(when = When.MAYBE)
3636
String jsr305NullableReturnWhen();
37+
38+
@jakarta.annotation.Nonnull
39+
String jakartaNonnullReturnWhen();
40+
41+
@jakarta.annotation.Nullable
42+
String jakartaNullableReturnWhen();
3743
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2017-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.util.nonnull.jakarta;
17+
18+
import jakarta.annotation.Nullable;
19+
20+
/**
21+
* @author Mark Paluch
22+
*/
23+
public interface NonNullOnPackage {
24+
25+
String someMethod(String arg, @Nullable String nullableArg);
26+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* @author Mark Paluch
19+
*/
20+
@Nonnull
21+
package org.springframework.data.util.nonnull.jakarta;
22+
23+
import jakarta.annotation.Nonnull;

0 commit comments

Comments
 (0)