Switch to JSpecify annotations

This commit updates the whole Spring Framework codebase to use JSpecify
annotations instead of Spring null-safety annotations with JSR 305
semantics.

JSpecify provides signficant enhancements such as properly defined
specifications, a canonical dependency with no split-package issue,
better tooling, better Kotlin integration and the capability to specify
generic type, array and varargs element null-safety. Generic type
null-safety is not defined by this commit yet and will be specified
later.

A key difference is that Spring null-safety annotations, following
JSR 305 semantics, apply to fields, parameters and return values,
while JSpecify annotations apply to type usages. That's why this
commit moves nullability annotations closer to the type for fields
and return values.

See gh-28797
This commit is contained in:
Sébastien Deleuze
2024-12-03 15:22:37 +01:00
parent fcb8aed03f
commit bc5d771a06
3459 changed files with 14118 additions and 22059 deletions

View File

@@ -18,6 +18,7 @@ package org.springframework.aot.generate;
import javax.lang.model.element.Modifier;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
@@ -26,7 +27,6 @@ import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.MethodSpec.Builder;
import org.springframework.javapoet.TypeName;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;

View File

@@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import javax.lang.model.element.Modifier;
import org.assertj.core.api.AbstractStringAssert;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.GeneratedFiles.FileHandler;
@@ -34,7 +35,6 @@ import org.springframework.core.io.Resource;
import org.springframework.javapoet.JavaFile;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.TypeSpec;
import org.springframework.lang.Nullable;
import org.springframework.util.function.ThrowingConsumer;
import static org.assertj.core.api.Assertions.assertThat;
@@ -226,11 +226,9 @@ class GeneratedFilesTests {
static class TestGeneratedFiles implements GeneratedFiles {
@Nullable
private Kind kind;
private @Nullable Kind kind;
@Nullable
private String path;
private @Nullable String path;
private TestFileHandler fileHandler = new TestFileHandler();
@@ -256,8 +254,7 @@ class GeneratedFilesTests {
private static class GeneratedFileAssert extends AbstractStringAssert<GeneratedFileAssert> {
@Nullable
private final Boolean override;
private final @Nullable Boolean override;
GeneratedFileAssert(InputStreamSource content, @Nullable Boolean override) throws IOException {
super(readSource(content), GeneratedFileAssert.class);
@@ -272,11 +269,9 @@ class GeneratedFilesTests {
private static class TestFileHandler extends FileHandler {
@Nullable
private InputStreamSource content;
private @Nullable InputStreamSource content;
@Nullable
private Boolean override;
private @Nullable Boolean override;
TestFileHandler(@Nullable InputStreamSource content) {
super(content != null, () -> content);

View File

@@ -33,6 +33,7 @@ import java.util.Set;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AssertProvider;
import org.assertj.core.api.StringAssert;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
@@ -47,7 +48,6 @@ import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.FieldSpec;
import org.springframework.javapoet.JavaFile;
import org.springframework.javapoet.TypeSpec;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

View File

@@ -22,9 +22,9 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -246,8 +246,7 @@ class ReflectionHintsTests {
@SuppressWarnings("unused")
static class TestType {
@Nullable
private String field;
private @Nullable String field;
void setName(String name) {

View File

@@ -237,7 +237,19 @@ class MethodParameterTests {
assertThat(m3.getTypeIndexForCurrentLevel()).isEqualTo(3);
}
public int method(String p1, long p2) {
@Test
void nullableWithSpringAnnotation() {
MethodParameter m = MethodParameter.forExecutable(method, 1);
assertThat(m.isOptional()).isTrue();
}
@Test
void nullableWithJSpecifyAnnotation() {
MethodParameter m = MethodParameter.forExecutable(method, 0);
assertThat(m.isOptional()).isTrue();
}
public int method(@org.jspecify.annotations.Nullable String p1, @org.springframework.lang.Nullable long p2) {
return 42;
}

View File

@@ -36,6 +36,7 @@ import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.meta.When;
import jakarta.annotation.Resource;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -47,7 +48,6 @@ import org.springframework.core.annotation.AnnotationUtilsTests.WebMapping;
import org.springframework.core.testfixture.stereotype.Component;
import org.springframework.core.testfixture.stereotype.Indexed;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap;
import static java.util.Arrays.asList;
@@ -1548,15 +1548,13 @@ class AnnotatedElementUtilsTests {
interface TransactionalService {
@Transactional
@Nullable
Object doIt();
@Nullable Object doIt();
}
class TransactionalServiceImpl implements TransactionalService {
@Override
@Nullable
public Object doIt() {
public @Nullable Object doIt() {
return null;
}
}

View File

@@ -21,9 +21,10 @@ import java.lang.annotation.RetentionPolicy;
import javax.annotation.Nonnull;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.lang.Nullable;
import org.springframework.lang.Contract;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -70,7 +71,7 @@ class AnnotationFilterTests {
@Test
void plainWhenSpringLangAnnotationReturnsTrue() {
assertThat(AnnotationFilter.PLAIN.matches(Nullable.class)).isTrue();
assertThat(AnnotationFilter.PLAIN.matches(Contract.class)).isTrue();
}
@Test

View File

@@ -476,14 +476,12 @@ class AnnotationTypeMappingsTests {
return result;
}
@Nullable
private Method getAliasMapping(AnnotationTypeMapping mapping, int attributeIndex) {
private @Nullable Method getAliasMapping(AnnotationTypeMapping mapping, int attributeIndex) {
int mapped = mapping.getAliasMapping(attributeIndex);
return mapped != -1 ? mapping.getRoot().getAttributes().get(mapped) : null;
}
@Nullable
private Method getConventionMapping(AnnotationTypeMapping mapping, int attributeIndex) {
private @Nullable Method getConventionMapping(AnnotationTypeMapping mapping, int attributeIndex) {
int mapped = mapping.getConventionMapping(attributeIndex);
return mapped != -1 ? mapping.getRoot().getAttributes().get(mapped) : null;
}

View File

@@ -40,7 +40,6 @@ import org.springframework.core.Ordered;
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
import org.springframework.core.testfixture.ide.IdeUtils;
import org.springframework.core.testfixture.stereotype.Component;
import org.springframework.lang.NonNullApi;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
@@ -429,8 +428,7 @@ class AnnotationUtilsTests {
@Test
void isAnnotationMetaPresentForPlainType() {
assertThat(isAnnotationMetaPresent(Order.class, Documented.class)).isTrue();
assertThat(isAnnotationMetaPresent(NonNullApi.class, Documented.class)).isTrue();
assertThat(isAnnotationMetaPresent(NonNullApi.class, Nonnull.class)).isTrue();
assertThat(isAnnotationMetaPresent(ParametersAreNonnullByDefault.class, Documented.class)).isTrue();
assertThat(isAnnotationMetaPresent(ParametersAreNonnullByDefault.class, Nonnull.class)).isTrue();
}

View File

@@ -29,11 +29,11 @@ import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.core.annotation.MergedAnnotations.Search;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
@@ -469,15 +469,13 @@ class AnnotationsScannerTests {
new AnnotationsProcessor<Object, String>() {
@Override
@Nullable
public String doWithAggregate(Object context, int aggregateIndex) {
return "";
}
@Override
@Nullable
public String doWithAnnotations(Object context, int aggregateIndex,
Object source, Annotation[] annotations) {
@Nullable Object source, Annotation[] annotations) {
throw new IllegalStateException("Should not call");
}
@@ -503,15 +501,13 @@ class AnnotationsScannerTests {
new AnnotationsProcessor<Object, String>() {
@Override
@Nullable
public String doWithAnnotations(Object context, int aggregateIndex,
Object source, Annotation[] annotations) {
@Nullable Object source, Annotation[] annotations) {
return "K";
}
@Override
@Nullable
public String finish(String result) {
public String finish(@Nullable String result) {
return "O" + result;
}
@@ -793,13 +789,11 @@ class AnnotationsScannerTests {
interface IgnorableOverrideInterface1 {
@Nullable
void method();
}
interface IgnorableOverrideInterface2 {
@Nullable
void method();
}

View File

@@ -36,6 +36,7 @@ import java.util.NoSuchElementException;
import java.util.stream.Stream;
import jakarta.annotation.Resource;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -49,7 +50,6 @@ import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
import org.springframework.core.testfixture.ide.IdeUtils;
import org.springframework.core.testfixture.stereotype.Component;
import org.springframework.core.testfixture.stereotype.Indexed;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
@@ -2875,8 +2875,7 @@ class MergedAnnotationsTests {
interface NullableAnnotatedInterface {
@Nullable
void fromInterfaceImplementedByRoot();
@Nullable String fromInterfaceImplementedByRoot();
}
static class Root implements AnnotatedInterface {

View File

@@ -18,6 +18,7 @@ package org.springframework.core.codec;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@@ -29,7 +30,6 @@ import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.testfixture.codec.AbstractEncoderTests;
import org.springframework.lang.Nullable;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;

View File

@@ -43,6 +43,7 @@ import java.util.UUID;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter;
@@ -50,7 +51,6 @@ import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -30,6 +30,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.core.convert.ConversionFailedException;
@@ -41,7 +42,6 @@ import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.io.DescriptiveResource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import static java.util.Comparator.naturalOrder;
@@ -681,8 +681,7 @@ class GenericConversionServiceTests {
}
@Override
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return null;
}
}
@@ -704,8 +703,7 @@ class GenericConversionServiceTests {
}
@Override
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return null;
}

View File

@@ -20,10 +20,9 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -109,13 +108,11 @@ class CustomEnvironmentTests {
void withNoProfileProperties() {
ConfigurableEnvironment env = new AbstractEnvironment() {
@Override
@Nullable
protected String doGetActiveProfilesProperty() {
protected @Nullable String doGetActiveProfilesProperty() {
return null;
}
@Override
@Nullable
protected String doGetDefaultProfilesProperty() {
protected @Nullable String doGetDefaultProfilesProperty() {
return null;
}
};
@@ -143,8 +140,7 @@ class CustomEnvironmentTests {
super(propertySources);
}
@Override
@Nullable
public String getProperty(String key) {
public @Nullable String getProperty(String key) {
return super.getProperty(key) + "-test";
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.core.type;
import java.net.URL;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.Resource;
@@ -26,7 +27,6 @@ import org.springframework.core.testfixture.EnabledForTestGroups;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING;

View File

@@ -31,10 +31,9 @@ import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Vector;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
/**

View File

@@ -26,9 +26,9 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.lang.Nullable;
import org.springframework.util.ConcurrentReferenceHashMap.Entry;
import org.springframework.util.ConcurrentReferenceHashMap.Reference;
import org.springframework.util.ConcurrentReferenceHashMap.Restructure;

View File

@@ -29,6 +29,7 @@ import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.sax.SAXSource;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.invocation.InvocationOnMock;
@@ -44,7 +45,6 @@ import org.xml.sax.helpers.AttributesImpl;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.tests.MockitoUtils;
import org.springframework.tests.MockitoUtils.InvocationArgumentsAdapter;