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:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user