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

@@ -31,7 +31,7 @@ import java.util.Locale;
import java.util.ResourceBundle;
import java.util.stream.Stream;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Instrumented version of JDK methods to be used by bytecode rewritten by the {@link RuntimeHintsAgent}.
@@ -232,8 +232,7 @@ public abstract class InstrumentedBridgeMethods {
return result;
}
@Nullable
public static URL classgetResource(Class<?> clazz, String name) {
public static @Nullable URL classgetResource(Class<?> clazz, String name) {
URL result = clazz.getResource(name);
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE)
.onInstance(clazz).withArgument(name).returnValue(result).build();
@@ -241,8 +240,7 @@ public abstract class InstrumentedBridgeMethods {
return result;
}
@Nullable
public static InputStream classgetResourceAsStream(Class<?> clazz, String name) {
public static @Nullable InputStream classgetResourceAsStream(Class<?> clazz, String name) {
InputStream result = clazz.getResourceAsStream(name);
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCEASSTREAM)
.onInstance(clazz).withArgument(name).returnValue(result).build();
@@ -267,8 +265,7 @@ public abstract class InstrumentedBridgeMethods {
return result;
}
@Nullable
public static URL classloadergetResource(ClassLoader classLoader, String name) {
public static @Nullable URL classloadergetResource(ClassLoader classLoader, String name) {
URL result = classLoader.getResource(name);
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASSLOADER_GETRESOURCE)
.onInstance(classLoader).withArgument(name).returnValue(result).build();
@@ -276,8 +273,7 @@ public abstract class InstrumentedBridgeMethods {
return result;
}
@Nullable
public static InputStream classloadergetResourceAsStream(ClassLoader classLoader, String name) {
public static @Nullable InputStream classloadergetResourceAsStream(ClassLoader classLoader, String name) {
InputStream result = classLoader.getResourceAsStream(name);
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASSLOADER_GETRESOURCEASSTREAM)
.onInstance(classLoader).withArgument(name).returnValue(result).build();

View File

@@ -21,8 +21,9 @@ import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Arrays;
import org.jspecify.annotations.Nullable;
import org.springframework.asm.ClassReader;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**

View File

@@ -18,7 +18,7 @@ package org.springframework.aot.agent;
import java.util.Objects;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Reference to a Java method, identified by its owner class and the method name.

View File

@@ -20,9 +20,10 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -36,15 +37,13 @@ import org.springframework.util.Assert;
*/
public final class RecordedInvocation {
@Nullable
private final Object instance;
private final @Nullable Object instance;
private final InstrumentedMethod instrumentedMethod;
private final Object[] arguments;
@Nullable
private final Object returnValue;
private final @Nullable Object returnValue;
private final List<StackWalker.StackFrame> stackFrames;
@@ -160,8 +159,7 @@ public final class RecordedInvocation {
* @return the value returned by the invocation
*/
@SuppressWarnings("unchecked")
@Nullable
public <T> T getReturnValue() {
public <T> @Nullable T getReturnValue() {
return (T) this.returnValue;
}
@@ -192,15 +190,13 @@ public final class RecordedInvocation {
*/
public static class Builder {
@Nullable
private Object instance;
private @Nullable Object instance;
private final InstrumentedMethod instrumentedMethod;
private Object[] arguments = new Object[0];
@Nullable
private Object returnValue;
private @Nullable Object returnValue;
Builder(InstrumentedMethod instrumentedMethod) {
@@ -234,7 +230,7 @@ public final class RecordedInvocation {
* @param arguments the invocation arguments
* @return {@code this}, to facilitate method chaining
*/
public Builder withArguments(@Nullable Object... arguments) {
public Builder withArguments(Object @Nullable ... arguments) {
if (arguments != null) {
this.arguments = arguments;
}

View File

@@ -20,8 +20,9 @@ import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**

View File

@@ -1,9 +1,7 @@
/**
* Support for recording method invocations relevant to {@link org.springframework.aot.hint.RuntimeHints} metadata.
*/
@NonNullApi
@NonNullFields
@NullMarked
package org.springframework.aot.agent;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;
import org.jspecify.annotations.NullMarked;

View File

@@ -1,9 +1,7 @@
/**
* Testing support for the {@link org.springframework.aot.agent.RuntimeHintsAgent}.
*/
@NonNullApi
@NonNullFields
@NullMarked
package org.springframework.aot.test.agent;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;
import org.jspecify.annotations.NullMarked;

View File

@@ -1,9 +1,7 @@
/**
* Test support for core AOT classes.
*/
@NonNullApi
@NonNullFields
@NullMarked
package org.springframework.aot.test.generate;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;
import org.jspecify.annotations.NullMarked;

View File

@@ -24,8 +24,9 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.jspecify.annotations.Nullable;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.lang.Nullable;
/**
* Simple mock {@link SpringFactoriesLoader} implementation that can be used for testing
@@ -67,9 +68,8 @@ public class MockSpringFactoriesLoader extends SpringFactoriesLoader {
@Override
@Nullable
@SuppressWarnings("unchecked")
protected <T> T instantiateFactory(String implementationName, Class<T> type,
protected <T> @Nullable T instantiateFactory(String implementationName, Class<T> type,
@Nullable ArgumentResolver argumentResolver, FailureHandler failureHandler) {
if (implementationName.startsWith("!")) {
Object implementation = this.implementations.get(implementationName);

View File

@@ -1,9 +1,7 @@
/**
* Test support classes for Spring's I/O support.
*/
@NonNullApi
@NonNullFields
@NullMarked
package org.springframework.core.test.io.support;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;
import org.jspecify.annotations.NullMarked;

View File

@@ -23,7 +23,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Stream;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* An immutable collection of {@link ClassFile} instances.
@@ -112,8 +112,7 @@ public final class ClassFiles implements Iterable<ClassFile> {
* @param name the fully qualified name to find
* @return a {@link ClassFile} instance or {@code null}
*/
@Nullable
public ClassFile get(String name) {
public @Nullable ClassFile get(String name) {
return this.files.get(name);
}

View File

@@ -22,7 +22,7 @@ import java.net.URL;
import java.util.Enumeration;
import java.util.function.Function;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* {@link ClassLoader} implementation to support
@@ -73,8 +73,7 @@ final class CompileWithForkedClassLoaderClassLoader extends ClassLoader {
return (bytes != null ? defineClass(name, bytes, 0, bytes.length, null) : super.findClass(name));
}
@Nullable
private byte[] findClassBytes(String name) {
private byte @Nullable [] findClassBytes(String name) {
byte[] bytes = this.classResourceLookup.apply(name);
if (bytes != null) {
return bytes;
@@ -98,8 +97,7 @@ final class CompileWithForkedClassLoaderClassLoader extends ClassLoader {
}
@Override
@Nullable
protected URL findResource(String name) {
protected @Nullable URL findResource(String name) {
return this.testClassLoader.getResource(name);
}

View File

@@ -21,7 +21,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@@ -38,8 +39,7 @@ public class Compiled {
private final ResourceFiles resourceFiles;
@Nullable
private List<Class<?>> compiledClasses;
private @Nullable List<Class<?>> compiledClasses;
Compiled(ClassLoader classLoader, SourceFiles sourceFiles, ResourceFiles resourceFiles) {

View File

@@ -26,7 +26,7 @@ import java.net.URI;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* In-memory {@link JavaFileObject} used to hold class bytecode.
@@ -38,8 +38,7 @@ class DynamicClassFileObject extends SimpleJavaFileObject {
private final String className;
@Nullable
private volatile byte[] bytes;
private volatile byte @Nullable [] bytes;
DynamicClassFileObject(String className) {
@@ -80,8 +79,7 @@ class DynamicClassFileObject extends SimpleJavaFileObject {
return this.className;
}
@Nullable
byte[] getBytes() {
byte @Nullable [] getBytes() {
return this.bytes;
}

View File

@@ -29,7 +29,8 @@ import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
@@ -52,8 +53,7 @@ public class DynamicClassLoader extends ClassLoader {
private final Map<String, DynamicResourceFileObject> dynamicResourceFiles;
@Nullable
private final Method defineClassMethod;
private final @Nullable Method defineClassMethod;
public DynamicClassLoader(ClassLoader parent, ClassFiles classFiles, ResourceFiles resourceFiles,
@@ -89,8 +89,7 @@ public class DynamicClassLoader extends ClassLoader {
return (clazz != null ? clazz : super.findClass(name));
}
@Nullable
private Class<?> defineClass(String name, @Nullable byte[] bytes) {
private @Nullable Class<?> defineClass(String name, byte @Nullable [] bytes) {
if (bytes == null) {
return null;
}
@@ -111,8 +110,7 @@ public class DynamicClassLoader extends ClassLoader {
}
@Override
@Nullable
protected URL findResource(String name) {
protected @Nullable URL findResource(String name) {
if (name.endsWith(ClassUtils.CLASS_FILE_SUFFIX)) {
String className = ClassUtils.convertResourcePathToClassName(name.substring(0,
name.length() - ClassUtils.CLASS_FILE_SUFFIX.length()));
@@ -132,8 +130,7 @@ public class DynamicClassLoader extends ClassLoader {
return super.findResource(name);
}
@Nullable
private byte[] findClassBytes(String name) {
private byte @Nullable [] findClassBytes(String name) {
ClassFile classFile = this.classFiles.get(name);
if (classFile != null) {
return classFile.getContent();
@@ -162,8 +159,7 @@ public class DynamicClassLoader extends ClassLoader {
private static class SingletonEnumeration<E> implements Enumeration<E> {
@Nullable
private E element;
private @Nullable E element;
SingletonEnumeration(@Nullable E element) {
@@ -177,8 +173,7 @@ public class DynamicClassLoader extends ClassLoader {
}
@Override
@Nullable
public E nextElement() {
public @Nullable E nextElement() {
E next = this.element;
this.element = null;
return next;

View File

@@ -19,7 +19,8 @@ package org.springframework.core.test.tools;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**

View File

@@ -17,8 +17,7 @@
package org.springframework.core.test.tools;
import org.assertj.core.api.AbstractAssert;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -25,7 +25,7 @@ import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Internal class used by {@link SourceFiles} and {@link ResourceFiles} to
@@ -83,8 +83,7 @@ final class DynamicFiles<F extends DynamicFile> implements Iterable<F> {
return this.files.isEmpty();
}
@Nullable
F get(String path) {
@Nullable F get(String path) {
return this.files.get(path);
}

View File

@@ -26,7 +26,7 @@ import java.net.URI;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* In-memory {@link JavaFileObject} used to hold generated resource file contents.
@@ -37,8 +37,7 @@ import org.springframework.lang.Nullable;
*/
class DynamicResourceFileObject extends SimpleJavaFileObject {
@Nullable
private volatile byte[] bytes;
private volatile byte @Nullable [] bytes;
DynamicResourceFileObject(String fileName) {
@@ -73,8 +72,7 @@ class DynamicResourceFileObject extends SimpleJavaFileObject {
this.bytes = bytes;
}
@Nullable
byte[] getBytes() {
byte @Nullable [] getBytes() {
return this.bytes;
}

View File

@@ -19,7 +19,7 @@ package org.springframework.core.test.tools;
import java.util.Iterator;
import java.util.stream.Stream;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* An immutable collection of {@link ResourceFile} instances.
@@ -115,8 +115,7 @@ public final class ResourceFiles implements Iterable<ResourceFile> {
* @param path the path to find
* @return a {@link ResourceFile} instance or {@code null}
*/
@Nullable
public ResourceFile get(String path) {
public @Nullable ResourceFile get(String path) {
return this.files.get(path);
}

View File

@@ -29,9 +29,9 @@ import com.thoughtworks.qdox.JavaProjectBuilder;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaSource;
import org.assertj.core.api.AssertProvider;
import org.jspecify.annotations.Nullable;
import org.springframework.core.io.InputStreamSource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.FileCopyUtils;

View File

@@ -20,7 +20,8 @@ import java.util.Iterator;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.util.ClassUtils;
/**
@@ -117,8 +118,7 @@ public final class SourceFiles implements Iterable<SourceFile> {
* @param path the path to find
* @return a {@link SourceFile} instance or {@code null}
*/
@Nullable
public SourceFile get(String path) {
public @Nullable SourceFile get(String path) {
return this.files.get(path);
}

View File

@@ -35,7 +35,7 @@ import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Utility that can be used to dynamically compile and test Java source code.
@@ -48,8 +48,7 @@ import org.springframework.lang.Nullable;
*/
public final class TestCompiler {
@Nullable
private final ClassLoader classLoader;
private final @Nullable ClassLoader classLoader;
private final JavaCompiler compiler;

View File

@@ -1,9 +1,7 @@
/**
* Support classes for compiling and testing generated code.
*/
@NonNullApi
@NonNullFields
@NullMarked
package org.springframework.core.test.tools;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;
import org.jspecify.annotations.NullMarked;