diff --git a/spring-core-test/src/main/java/org/springframework/core/test/tools/CompileWithForkedClassLoaderClassLoader.java b/spring-core-test/src/main/java/org/springframework/core/test/tools/CompileWithForkedClassLoaderClassLoader.java index 8d626d6823b..36bf8512109 100644 --- a/spring-core-test/src/main/java/org/springframework/core/test/tools/CompileWithForkedClassLoaderClassLoader.java +++ b/spring-core-test/src/main/java/org/springframework/core/test/tools/CompileWithForkedClassLoaderClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ final class CompileWithForkedClassLoaderClassLoader extends ClassLoader { private final ClassLoader testClassLoader; - private Function classResourceLookup = name -> null; + private Function classResourceLookup = name -> null; public CompileWithForkedClassLoaderClassLoader(ClassLoader testClassLoader) { @@ -48,7 +48,7 @@ final class CompileWithForkedClassLoaderClassLoader extends ClassLoader { // Invoked reflectively by DynamicClassLoader @SuppressWarnings("unused") - void setClassResourceLookup(Function classResourceLookup) { + void setClassResourceLookup(Function classResourceLookup) { this.classResourceLookup = classResourceLookup; } diff --git a/spring-core-test/src/main/java/org/springframework/core/test/tools/DynamicClassLoader.java b/spring-core-test/src/main/java/org/springframework/core/test/tools/DynamicClassLoader.java index 3a95b7e53f0..ed8f58d5a77 100644 --- a/spring-core-test/src/main/java/org/springframework/core/test/tools/DynamicClassLoader.java +++ b/spring-core-test/src/main/java/org/springframework/core/test/tools/DynamicClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,7 +71,7 @@ public class DynamicClassLoader extends ClassLoader { "setClassResourceLookup", Function.class); ReflectionUtils.makeAccessible(setClassResourceLookupMethod); ReflectionUtils.invokeMethod(setClassResourceLookupMethod, - getParent(), (Function) this::findClassBytes); + getParent(), (Function) this::findClassBytes); this.defineClassMethod = lookupMethod(parentClass, "defineDynamicClass", String.class, byte[].class, int.class, int.class); ReflectionUtils.makeAccessible(this.defineClassMethod); diff --git a/spring-core/src/main/java/org/springframework/aot/generate/MethodReference.java b/spring-core/src/main/java/org/springframework/aot/generate/MethodReference.java index 42ff4169aa8..32027c7189f 100644 --- a/spring-core/src/main/java/org/springframework/aot/generate/MethodReference.java +++ b/spring-core/src/main/java/org/springframework/aot/generate/MethodReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -106,7 +106,7 @@ public interface MethodReference { * @param function the resolver function * @return a new {@link ArgumentCodeGenerator} instance backed by the function */ - static ArgumentCodeGenerator from(Function function) { + static ArgumentCodeGenerator from(Function function) { return function::apply; } diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index 37c46af7814..da0535ece0e 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -706,7 +706,7 @@ public class MethodParameter { } ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer; if (discoverer != null) { - String[] parameterNames = null; + @Nullable String[] parameterNames = null; if (this.executable instanceof Method method) { parameterNames = discoverer.getParameterNames(method); } @@ -865,7 +865,7 @@ public class MethodParameter { * @return the corresponding MethodParameter instance * @since 6.1 */ - public static MethodParameter forFieldAwareConstructor(Constructor ctor, int parameterIndex, String fieldName) { + public static MethodParameter forFieldAwareConstructor(Constructor ctor, int parameterIndex, @Nullable String fieldName) { return new FieldAwareConstructorParameter(ctor, parameterIndex, fieldName); } @@ -877,7 +877,7 @@ public class MethodParameter { private volatile Annotation @Nullable [] combinedAnnotations; - public FieldAwareConstructorParameter(Constructor constructor, int parameterIndex, String fieldName) { + public FieldAwareConstructorParameter(Constructor constructor, int parameterIndex, @Nullable String fieldName) { super(constructor, parameterIndex); this.parameterName = fieldName; } diff --git a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java index 9deb8948906..f816cd27d4a 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,15 @@ package org.springframework.core; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.Flow; import java.util.function.Function; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; import org.jspecify.annotations.Nullable; import org.reactivestreams.FlowAdapters; import org.reactivestreams.Publisher; @@ -380,13 +383,13 @@ public class ReactiveAdapterRegistry { io.smallrye.mutiny.groups.MultiCreate.class, "publisher", Flow.Publisher.class); registry.registerReactiveType(uniDesc, uni -> FlowAdapters.toPublisher((Flow.Publisher) - ReflectionUtils.invokeMethod(uniToPublisher, ((io.smallrye.mutiny.Uni) uni).convert())), - publisher -> ReflectionUtils.invokeMethod(uniPublisher, io.smallrye.mutiny.Uni.createFrom(), - FlowAdapters.toFlowPublisher(publisher))); + Objects.requireNonNull(ReflectionUtils.invokeMethod(uniToPublisher, ((Uni) uni).convert()))), + publisher -> Objects.requireNonNull(ReflectionUtils.invokeMethod(uniPublisher, Uni.createFrom(), + FlowAdapters.toFlowPublisher(publisher)))); registry.registerReactiveType(multiDesc, multi -> FlowAdapters.toPublisher((Flow.Publisher) multi), - publisher -> ReflectionUtils.invokeMethod(multiPublisher, io.smallrye.mutiny.Multi.createFrom(), - FlowAdapters.toFlowPublisher(publisher))); + publisher -> Objects.requireNonNull(ReflectionUtils.invokeMethod(multiPublisher, Multi.createFrom(), + FlowAdapters.toFlowPublisher(publisher)))); } else { // Mutiny 1 based on Reactive Streams diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index b6428acab82..ab60e074b57 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -825,9 +825,9 @@ public class ResolvableType implements Serializable { * @see #getGenerics() * @see #resolve() */ - public Class[] resolveGenerics() { + public @Nullable Class[] resolveGenerics() { ResolvableType[] generics = getGenerics(); - Class[] resolvedGenerics = new Class[generics.length]; + @Nullable Class[] resolvedGenerics = new Class[generics.length]; for (int i = 0; i < generics.length; i++) { resolvedGenerics[i] = generics[i].resolve(); } diff --git a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java index 9a568e07a5a..508081bddc2 100644 --- a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java +++ b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -205,7 +205,7 @@ final class SerializableTypeWrapper { if (returnValue == null) { return null; } - Type[] result = new Type[((Type[]) returnValue).length]; + @Nullable Type[] result = new Type[((Type[]) returnValue).length]; for (int i = 0; i < result.length; i++) { result[i] = forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, i)); } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java index 83d22e58bc9..132e35c9bfd 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -478,7 +478,7 @@ public abstract class AnnotatedElementUtils { * attributes from all annotations found, or {@code null} if not found * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean) */ - public static @Nullable MultiValueMap getAllAnnotationAttributes( + public static MultiValueMap getAllAnnotationAttributes( AnnotatedElement element, String annotationName) { return getAllAnnotationAttributes(element, annotationName, false, false); @@ -502,7 +502,7 @@ public abstract class AnnotatedElementUtils { * @return a {@link MultiValueMap} keyed by attribute name, containing the annotation * attributes from all annotations found, or {@code null} if not found */ - public static @Nullable MultiValueMap getAllAnnotationAttributes(AnnotatedElement element, + public static MultiValueMap getAllAnnotationAttributes(AnnotatedElement element, String annotationName, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) { Adapt[] adaptations = Adapt.values(classValuesAsString, nestedAnnotationsAsMap); diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java index 30f43f90b84..c4d8829a4ba 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,7 +45,7 @@ import org.springframework.util.StringUtils; * @see AnnotatedElementUtils */ @SuppressWarnings("serial") -public class AnnotationAttributes extends LinkedHashMap { +public class AnnotationAttributes extends LinkedHashMap { private static final String UNKNOWN = "unknown"; @@ -83,7 +83,7 @@ public class AnnotationAttributes extends LinkedHashMap { * @param map original source of annotation attribute key-value pairs * @see #fromMap(Map) */ - public AnnotationAttributes(Map map) { + public AnnotationAttributes(Map map) { super(map); this.annotationType = null; this.displayName = UNKNOWN; @@ -376,10 +376,10 @@ public class AnnotationAttributes extends LinkedHashMap { @Override public String toString() { - Iterator> entries = entrySet().iterator(); + Iterator> entries = entrySet().iterator(); StringBuilder sb = new StringBuilder("{"); while (entries.hasNext()) { - Map.Entry entry = entries.next(); + Map.Entry entry = entries.next(); sb.append(entry.getKey()); sb.append('='); sb.append(valueToString(entry.getValue())); @@ -391,7 +391,7 @@ public class AnnotationAttributes extends LinkedHashMap { return sb.toString(); } - private String valueToString(Object value) { + private String valueToString(@Nullable Object value) { if (value == this) { return "(this Map)"; } @@ -410,7 +410,7 @@ public class AnnotationAttributes extends LinkedHashMap { * to the {@link #AnnotationAttributes(Map)} constructor. * @param map original source of annotation attribute key-value pairs */ - public static @Nullable AnnotationAttributes fromMap(@Nullable Map map) { + public static @Nullable AnnotationAttributes fromMap(@Nullable Map map) { if (map == null) { return null; } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java index e26201476d0..b57a4a27072 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import java.util.Set; import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; import org.springframework.util.ConcurrentReferenceHashMap; /** @@ -87,7 +88,7 @@ final class AnnotationTypeMappings { } private void addMetaAnnotationsToQueue(Deque queue, AnnotationTypeMapping source) { - Annotation[] metaAnnotations = AnnotationsScanner.getDeclaredAnnotations(source.getAnnotationType(), false); + @Nullable Annotation[] metaAnnotations = AnnotationsScanner.getDeclaredAnnotations(source.getAnnotationType(), false); for (Annotation metaAnnotation : metaAnnotations) { if (!isMappable(source, metaAnnotation)) { continue; @@ -127,6 +128,7 @@ final class AnnotationTypeMappings { } } + @Contract("_, null -> false") private boolean isMappable(AnnotationTypeMapping source, @Nullable Annotation metaAnnotation) { return (metaAnnotation != null && !this.filter.matches(metaAnnotation) && !AnnotationFilter.PLAIN.matches(source.getAnnotationType()) && diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 8a6b393366d..cb6a15a3c7c 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -773,7 +773,7 @@ public abstract class AnnotationUtils { * @see #getAnnotationAttributes(Annotation, boolean, boolean) * @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean) */ - public static Map getAnnotationAttributes(Annotation annotation) { + public static Map getAnnotationAttributes(Annotation annotation) { return getAnnotationAttributes(null, annotation); } @@ -791,7 +791,7 @@ public abstract class AnnotationUtils { * corresponding attribute values as values (never {@code null}) * @see #getAnnotationAttributes(Annotation, boolean, boolean) */ - public static Map getAnnotationAttributes( + public static Map getAnnotationAttributes( Annotation annotation, boolean classValuesAsString) { return getAnnotationAttributes(annotation, classValuesAsString, false); diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsProcessor.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsProcessor.java index 8c1c81824eb..ef6a4a276e9 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsProcessor.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,7 +54,7 @@ interface AnnotationsProcessor { * {@code null} elements) * @return a {@code non-null} result if no further processing is required */ - @Nullable R doWithAnnotations(C context, int aggregateIndex, @Nullable Object source, Annotation[] annotations); + @Nullable R doWithAnnotations(C context, int aggregateIndex, @Nullable Object source, @Nullable Annotation[] annotations); /** * Get the final result to be returned. By default this method returns diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java index 8b09b1d5ce1..5274214e5bf 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ import org.springframework.util.ReflectionUtils; */ abstract class AnnotationsScanner { - private static final Annotation[] NO_ANNOTATIONS = {}; + private static final @Nullable Annotation[] NO_ANNOTATIONS = {}; private static final Method[] NO_METHODS = {}; @@ -55,7 +55,7 @@ abstract class AnnotationsScanner { private static final Map declaredAnnotationCache = new ConcurrentReferenceHashMap<>(256); - private static final Map, Method[]> baseTypeMethodsCache = + private static final Map, @Nullable Method[]> baseTypeMethodsCache = new ConcurrentReferenceHashMap<>(256); @@ -114,7 +114,7 @@ abstract class AnnotationsScanner { if (isWithoutHierarchy(source, Search.never)) { return processElement(context, source, processor); } - Annotation[] relevant = null; + @Nullable Annotation[] relevant = null; int remaining = Integer.MAX_VALUE; int aggregateIndex = 0; Class root = source; @@ -123,7 +123,7 @@ abstract class AnnotationsScanner { if (result != null) { return result; } - Annotation[] declaredAnns = getDeclaredAnnotations(source, true); + @Nullable Annotation[] declaredAnns = getDeclaredAnnotations(source, true); if (declaredAnns.length > 0) { if (relevant == null) { relevant = root.getAnnotations(); @@ -133,6 +133,7 @@ abstract class AnnotationsScanner { if (declaredAnns[i] != null) { boolean isRelevant = false; for (int relevantIndex = 0; relevantIndex < relevant.length; relevantIndex++) { + //noinspection DataFlowIssue if (relevant[relevantIndex] != null && declaredAnns[i].annotationType() == relevant[relevantIndex].annotationType()) { isRelevant = true; @@ -181,7 +182,7 @@ abstract class AnnotationsScanner { if (hasPlainJavaAnnotationsOnly(source)) { return null; } - Annotation[] annotations = getDeclaredAnnotations(source, false); + @Nullable Annotation[] annotations = getDeclaredAnnotations(source, false); result = processor.doWithAnnotations(context, aggregateIndex[0], source, annotations); if (result != null) { return result; @@ -320,16 +321,18 @@ abstract class AnnotationsScanner { return null; } - private static Method[] getBaseTypeMethods(C context, Class baseType) { + @SuppressWarnings("NullAway") // Dataflow analysis limitation + private static @Nullable Method[] getBaseTypeMethods(C context, Class baseType) { if (baseType == Object.class || hasPlainJavaAnnotationsOnly(baseType)) { return NO_METHODS; } - Method[] methods = baseTypeMethodsCache.get(baseType); + @Nullable Method[] methods = baseTypeMethodsCache.get(baseType); if (methods == null) { methods = ReflectionUtils.getDeclaredMethods(baseType); int cleared = 0; for (int i = 0; i < methods.length; i++) { + //noinspection DataFlowIssue if (Modifier.isPrivate(methods[i].getModifiers()) || hasPlainJavaAnnotationsOnly(methods[i]) || getDeclaredAnnotations(methods[i], false).length == 0) { @@ -385,14 +388,14 @@ abstract class AnnotationsScanner { private static @Nullable R processMethodAnnotations(C context, int aggregateIndex, Method source, AnnotationsProcessor processor) { - Annotation[] annotations = getDeclaredAnnotations(source, false); + @Nullable Annotation[] annotations = getDeclaredAnnotations(source, false); R result = processor.doWithAnnotations(context, aggregateIndex, source, annotations); if (result != null) { return result; } Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(source); if (bridgedMethod != source) { - Annotation[] bridgedAnnotations = getDeclaredAnnotations(bridgedMethod, true); + @Nullable Annotation[] bridgedAnnotations = getDeclaredAnnotations(bridgedMethod, true); for (int i = 0; i < bridgedAnnotations.length; i++) { if (ObjectUtils.containsElement(annotations, bridgedAnnotations[i])) { bridgedAnnotations[i] = null; @@ -419,7 +422,7 @@ abstract class AnnotationsScanner { @SuppressWarnings("unchecked") static @Nullable A getDeclaredAnnotation(AnnotatedElement source, Class annotationType) { - Annotation[] annotations = getDeclaredAnnotations(source, false); + @Nullable Annotation[] annotations = getDeclaredAnnotations(source, false); for (Annotation annotation : annotations) { if (annotation != null && annotationType == annotation.annotationType()) { return (A) annotation; @@ -428,9 +431,10 @@ abstract class AnnotationsScanner { return null; } - static Annotation[] getDeclaredAnnotations(AnnotatedElement source, boolean defensive) { + @SuppressWarnings("NullAway") // Dataflow analysis limitation + static @Nullable Annotation[] getDeclaredAnnotations(AnnotatedElement source, boolean defensive) { boolean cached = false; - Annotation[] annotations = declaredAnnotationCache.get(source); + @Nullable Annotation[] annotations = declaredAnnotationCache.get(source); if (annotations != null) { cached = true; } @@ -440,6 +444,7 @@ abstract class AnnotationsScanner { boolean allIgnored = true; for (int i = 0; i < annotations.length; i++) { Annotation annotation = annotations[i]; + //noinspection DataFlowIssue if (isIgnorable(annotation.annotationType()) || !AttributeMethods.forAnnotationType(annotation.annotationType()).canLoad(annotation)) { annotations[i] = null; @@ -450,6 +455,7 @@ abstract class AnnotationsScanner { } annotations = (allIgnored ? NO_ANNOTATIONS : annotations); if (source instanceof Class || source instanceof Member) { + //noinspection NullableProblems declaredAnnotationCache.put(source, annotations); cached = true; } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java b/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java index c524f355eb9..d805bfaa3d6 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -250,11 +250,13 @@ final class AttributeMethods { return cache.computeIfAbsent(annotationType, AttributeMethods::compute); } + @SuppressWarnings("NullAway") // Dataflow analysis limitation private static AttributeMethods compute(Class annotationType) { Method[] methods = annotationType.getDeclaredMethods(); int size = methods.length; for (int i = 0; i < methods.length; i++) { if (!isAttributeMethod(methods[i])) { + //noinspection DataFlowIssue methods[i] = null; size--; } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationCollectors.java b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationCollectors.java index d09369f9165..5a303534aa3 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationCollectors.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationCollectors.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,8 @@ import java.util.function.IntFunction; import java.util.stream.Collector; import java.util.stream.Collector.Characteristics; +import org.jspecify.annotations.Nullable; + import org.springframework.core.annotation.MergedAnnotation.Adapt; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -108,10 +110,10 @@ public abstract class MergedAnnotationCollectors { * annotations into a {@link LinkedMultiValueMap} * @see #toMultiValueMap(Function, MergedAnnotation.Adapt...) */ - public static Collector, ?, MultiValueMap> toMultiValueMap( + public static Collector, ? extends @Nullable Object, @Nullable MultiValueMap> toMultiValueMap( Adapt... adaptations) { - return toMultiValueMap(Function.identity(), adaptations); + return toMultiValueMap((MultiValueMap t) -> t, adaptations); } /** @@ -126,14 +128,14 @@ public abstract class MergedAnnotationCollectors { * annotations into a {@link LinkedMultiValueMap} * @see #toMultiValueMap(MergedAnnotation.Adapt...) */ - public static Collector, ?, MultiValueMap> toMultiValueMap( - Function, MultiValueMap> finisher, + public static Collector, ? extends @Nullable Object, @Nullable MultiValueMap> toMultiValueMap( + Function, @Nullable MultiValueMap> finisher, Adapt... adaptations) { Characteristics[] characteristics = (isSameInstance(finisher, Function.identity()) ? IDENTITY_FINISH_CHARACTERISTICS : NO_CHARACTERISTICS); return Collector.of(LinkedMultiValueMap::new, - (map, annotation) -> annotation.asMap(adaptations).forEach(map::add), + (MultiValueMap map, MergedAnnotation annotation) -> annotation.asMap(adaptations).forEach(map::add), MergedAnnotationCollectors::combiner, finisher, characteristics); } @@ -157,7 +159,7 @@ public abstract class MergedAnnotationCollectors { *

This method is only invoked if the {@link java.util.stream.Stream} is * processed in {@linkplain java.util.stream.Stream#parallel() parallel}. */ - private static MultiValueMap combiner(MultiValueMap map, MultiValueMap additions) { + private static MultiValueMap combiner(MultiValueMap map, MultiValueMap additions) { map.addAll(additions); return map; } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationPredicates.java b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationPredicates.java index dc0e1612bd6..9568829b9a1 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationPredicates.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationPredicates.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,8 +23,6 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; -import org.jspecify.annotations.Nullable; - import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -126,7 +124,8 @@ public abstract class MergedAnnotationPredicates { private boolean hasLastValue; - private @Nullable Object lastValue; + @SuppressWarnings("NullAway.Init") + private Object lastValue; FirstRunOfPredicate(Function, ?> valueExtractor) { Assert.notNull(valueExtractor, "Value extractor must not be null"); @@ -134,7 +133,7 @@ public abstract class MergedAnnotationPredicates { } @Override - public boolean test(@Nullable MergedAnnotation annotation) { + public boolean test(MergedAnnotation annotation) { if (!this.hasLastValue) { this.hasLastValue = true; this.lastValue = this.valueExtractor.apply(annotation); @@ -162,7 +161,7 @@ public abstract class MergedAnnotationPredicates { } @Override - public boolean test(@Nullable MergedAnnotation annotation) { + public boolean test(MergedAnnotation annotation) { K key = this.keyExtractor.apply(annotation); return this.seen.add(key); } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotations.java b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotations.java index c83b3328235..81b0b2c1926 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotations.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotations.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import java.util.function.Predicate; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; /** @@ -309,7 +310,7 @@ final class TypeMappedAnnotations implements MergedAnnotations { @Override public @Nullable Boolean doWithAnnotations(Object requiredType, int aggregateIndex, - @Nullable Object source, Annotation[] annotations) { + @Nullable Object source, @Nullable Annotation[] annotations) { for (Annotation annotation : annotations) { if (annotation != null) { @@ -388,7 +389,7 @@ final class TypeMappedAnnotations implements MergedAnnotations { @Override public @Nullable MergedAnnotation doWithAnnotations(Object type, int aggregateIndex, - @Nullable Object source, Annotation[] annotations) { + @Nullable Object source, @Nullable Annotation[] annotations) { for (Annotation annotation : annotations) { if (annotation != null && !annotationFilter.matches(annotation)) { @@ -450,24 +451,24 @@ final class TypeMappedAnnotations implements MergedAnnotations { @Override public @Nullable List doWithAnnotations(Object criteria, int aggregateIndex, - @Nullable Object source, Annotation[] annotations) { + @Nullable Object source, @Nullable Annotation[] annotations) { this.aggregates.add(createAggregate(aggregateIndex, source, annotations)); return null; } - private Aggregate createAggregate(int aggregateIndex, @Nullable Object source, Annotation[] annotations) { + private Aggregate createAggregate(int aggregateIndex, @Nullable Object source, @Nullable Annotation[] annotations) { List aggregateAnnotations = getAggregateAnnotations(annotations); return new Aggregate(aggregateIndex, source, aggregateAnnotations); } - private List getAggregateAnnotations(Annotation[] annotations) { + private List getAggregateAnnotations(@Nullable Annotation[] annotations) { List result = new ArrayList<>(annotations.length); addAggregateAnnotations(result, annotations); return result; } - private void addAggregateAnnotations(List aggregateAnnotations, Annotation[] annotations) { + private void addAggregateAnnotations(List aggregateAnnotations, @Nullable Annotation[] annotations) { for (Annotation annotation : annotations) { if (annotation != null && !annotationFilter.matches(annotation)) { Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(annotation); @@ -482,7 +483,7 @@ final class TypeMappedAnnotations implements MergedAnnotations { } @Override - public List finish(@Nullable List processResult) { + public @NonNull List finish(@Nullable List processResult) { return this.aggregates; } } diff --git a/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java b/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java index c2dc31a2c36..94129a481aa 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -584,11 +584,11 @@ public class SpringFactoriesLoader { * @param function the resolver function * @return a new {@link ArgumentResolver} instance backed by the function */ - static ArgumentResolver from(Function, Object> function) { + static ArgumentResolver from(Function, @Nullable Object> function) { return new ArgumentResolver() { @SuppressWarnings("unchecked") @Override - public T resolve(Class type) { + public @Nullable T resolve(Class type) { return (T) function.apply(type); } }; diff --git a/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java b/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java index 5fab2a3b195..5957199547b 100644 --- a/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,7 +87,7 @@ public interface AnnotatedTypeMetadata { * as map key (for example, "location") and the attribute's value as map value; or * {@code null} if no matching annotation is found */ - default @Nullable Map getAnnotationAttributes(String annotationName) { + default @Nullable Map getAnnotationAttributes(String annotationName) { return getAnnotationAttributes(annotationName, false); } @@ -106,7 +106,7 @@ public interface AnnotatedTypeMetadata { * as map key (for example, "location") and the attribute's value as map value; or * {@code null} if no matching annotation is found */ - default @Nullable Map getAnnotationAttributes(String annotationName, + default @Nullable Map getAnnotationAttributes(String annotationName, boolean classValuesAsString) { MergedAnnotation annotation = getAnnotations().get(annotationName, @@ -129,7 +129,7 @@ public interface AnnotatedTypeMetadata { * map value; or {@code null} if no matching annotation is found * @see #getAllAnnotationAttributes(String, boolean) */ - default @Nullable MultiValueMap getAllAnnotationAttributes(String annotationName) { + default @Nullable MultiValueMap getAllAnnotationAttributes(String annotationName) { return getAllAnnotationAttributes(annotationName, false); } @@ -148,7 +148,7 @@ public interface AnnotatedTypeMetadata { * map value; or {@code null} if no matching annotation is found * @see #getAllAnnotationAttributes(String) */ - default @Nullable MultiValueMap getAllAnnotationAttributes( + default @Nullable MultiValueMap getAllAnnotationAttributes( String annotationName, boolean classValuesAsString) { Adapt[] adaptations = Adapt.values(classValuesAsString, true); @@ -156,7 +156,7 @@ public interface AnnotatedTypeMetadata { .filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes)) .map(MergedAnnotation::withNonMergedAttributes) .collect(MergedAnnotationCollectors.toMultiValueMap( - map -> (map.isEmpty() ? null : map), adaptations)); + (MultiValueMap map) -> (map.isEmpty() ? null : map), adaptations)); } /** diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java index 33356252e5b..ec6064992c8 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -105,7 +105,7 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements } @Override - public @Nullable Map getAnnotationAttributes(String annotationName, boolean classValuesAsString) { + public @Nullable Map getAnnotationAttributes(String annotationName, boolean classValuesAsString) { if (this.nestedAnnotationsAsMap) { return AnnotationMetadata.super.getAnnotationAttributes(annotationName, classValuesAsString); } @@ -114,7 +114,8 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements } @Override - public @Nullable MultiValueMap getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) { + @SuppressWarnings("NullAway") // Null-safety of Java super method not yet managed + public @Nullable MultiValueMap getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) { if (this.nestedAnnotationsAsMap) { return AnnotationMetadata.super.getAllAnnotationAttributes(annotationName, classValuesAsString); } diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java index 7d300dd3b76..25ae1e1976d 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -131,7 +131,7 @@ public class StandardMethodMetadata implements MethodMetadata { } @Override - public @Nullable Map getAnnotationAttributes(String annotationName, boolean classValuesAsString) { + public @Nullable Map getAnnotationAttributes(String annotationName, boolean classValuesAsString) { if (this.nestedAnnotationsAsMap) { return MethodMetadata.super.getAnnotationAttributes(annotationName, classValuesAsString); } @@ -140,7 +140,8 @@ public class StandardMethodMetadata implements MethodMetadata { } @Override - public @Nullable MultiValueMap getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) { + @SuppressWarnings("NullAway") // Null-safety of Java super method not yet managed + public @Nullable MultiValueMap getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) { if (this.nestedAnnotationsAsMap) { return MethodMetadata.super.getAllAnnotationAttributes(annotationName, classValuesAsString); } diff --git a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java index 20ac40eadde..f92169d5f2d 100644 --- a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,7 +64,7 @@ public abstract class CollectionUtils { * @return whether the given Collection is empty */ @Contract("null -> true") - public static boolean isEmpty(@Nullable Collection collection) { + public static boolean isEmpty(@Nullable Collection collection) { return (collection == null || collection.isEmpty()); } @@ -75,7 +75,7 @@ public abstract class CollectionUtils { * @return whether the given Map is empty */ @Contract("null -> true") - public static boolean isEmpty(@Nullable Map map) { + public static boolean isEmpty(@Nullable Map map) { return (map == null || map.isEmpty()); } diff --git a/spring-core/src/main/java/org/springframework/util/CompositeCollection.java b/spring-core/src/main/java/org/springframework/util/CompositeCollection.java index 09e5864891c..a2783bb2f3c 100644 --- a/spring-core/src/main/java/org/springframework/util/CompositeCollection.java +++ b/spring-core/src/main/java/org/springframework/util/CompositeCollection.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ import java.lang.reflect.Array; import java.util.Collection; import java.util.Iterator; +import org.jspecify.annotations.Nullable; + /** * Composite collection that combines two other collections. This type is only @@ -83,10 +85,10 @@ class CompositeCollection implements Collection { } @Override - @SuppressWarnings("unchecked") - public T[] toArray(T[] a) { + @SuppressWarnings({"unchecked","NullAway"}) // Overridden method does not define nullness + public @Nullable T[] toArray(@Nullable T[] a) { int size = this.size(); - T[] result; + @Nullable T[] result; if (a.length >= size) { result = a; } diff --git a/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java b/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java index 9213241de10..0e2e33eddcb 100644 --- a/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java +++ b/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -456,7 +456,7 @@ public class ConcurrentReferenceHashMap extends AbstractMap implemen * Array of references indexed using the low order bits from the hash. * This property should only be set along with {@code resizeThreshold}. */ - private volatile Reference[] references; + private volatile @Nullable Reference[] references; /** * The total number of references contained in this segment. This includes chained @@ -485,7 +485,7 @@ public class ConcurrentReferenceHashMap extends AbstractMap implemen return null; } // Use a local copy to protect against other threads writing - Reference[] references = this.references; + @Nullable Reference[] references = this.references; int index = getIndex(hash, references); Reference head = references[index]; return findInChain(head, key, hash); @@ -641,7 +641,7 @@ public class ConcurrentReferenceHashMap extends AbstractMap implemen } } - private @Nullable Reference findInChain(Reference ref, @Nullable Object key, int hash) { + private @Nullable Reference findInChain(@Nullable Reference ref, @Nullable Object key, int hash) { Reference currRef = ref; while (currRef != null) { if (currRef.getHash() == hash) { @@ -663,7 +663,7 @@ public class ConcurrentReferenceHashMap extends AbstractMap implemen return new Reference[size]; } - private int getIndex(int hash, Reference[] references) { + private int getIndex(int hash, @Nullable Reference[] references) { return (hash & (references.length - 1)); } @@ -879,7 +879,7 @@ public class ConcurrentReferenceHashMap extends AbstractMap implemen private int referenceIndex; - private Reference @Nullable [] references; + private @Nullable Reference @Nullable [] references; private @Nullable Reference reference; diff --git a/spring-core/src/main/java/org/springframework/util/MethodInvoker.java b/spring-core/src/main/java/org/springframework/util/MethodInvoker.java index f17b0aca39e..eefcafea8c5 100644 --- a/spring-core/src/main/java/org/springframework/util/MethodInvoker.java +++ b/spring-core/src/main/java/org/springframework/util/MethodInvoker.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,7 +49,7 @@ public class MethodInvoker { private @Nullable String staticMethod; - private Object @Nullable [] arguments; + private @Nullable Object @Nullable [] arguments; /** The method we will call. */ private @Nullable Method methodObject; @@ -127,14 +127,14 @@ public class MethodInvoker { * Set arguments for the method invocation. If this property is not set, * or the Object array is of length 0, a method with no arguments is assumed. */ - public void setArguments(Object @Nullable ... arguments) { + public void setArguments(@Nullable Object... arguments) { this.arguments = arguments; } /** * Return the arguments for the method invocation. */ - public Object[] getArguments() { + public @Nullable Object[] getArguments() { return (this.arguments != null ? this.arguments : EMPTY_ARGUMENTS); } @@ -166,7 +166,7 @@ public class MethodInvoker { Assert.notNull(targetClass, "Either 'targetClass' or 'targetObject' is required"); Assert.notNull(targetMethod, "Property 'targetMethod' is required"); - Object[] arguments = getArguments(); + @Nullable Object[] arguments = getArguments(); Class[] argTypes = new Class[arguments.length]; for (int i = 0; i < arguments.length; ++i) { argTypes[i] = (arguments[i] != null ? arguments[i].getClass() : Object.class); @@ -206,7 +206,7 @@ public class MethodInvoker { */ protected @Nullable Method findMatchingMethod() { String targetMethod = getTargetMethod(); - Object[] arguments = getArguments(); + @Nullable Object[] arguments = getArguments(); int argCount = arguments.length; Class targetClass = getTargetClass(); diff --git a/spring-core/src/main/java/org/springframework/util/MultiValueMap.java b/spring-core/src/main/java/org/springframework/util/MultiValueMap.java index 195db529d05..fc6cd12fbf3 100644 --- a/spring-core/src/main/java/org/springframework/util/MultiValueMap.java +++ b/spring-core/src/main/java/org/springframework/util/MultiValueMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import org.jspecify.annotations.Nullable; * @param the key type * @param the value element type */ -public interface MultiValueMap extends Map> { +public interface MultiValueMap extends Map> { /** * Return the first value for the given key. diff --git a/spring-core/src/main/java/org/springframework/util/ObjectUtils.java b/spring-core/src/main/java/org/springframework/util/ObjectUtils.java index ad22311e99c..34a2be4e309 100644 --- a/spring-core/src/main/java/org/springframework/util/ObjectUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ObjectUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -188,7 +188,7 @@ public abstract class ObjectUtils { * @param element the element to check for * @return whether the element has been found in the given array */ - public static boolean containsElement(Object @Nullable [] array, Object element) { + public static boolean containsElement(@Nullable Object @Nullable [] array, @Nullable Object element) { if (array == null) { return false; } @@ -266,7 +266,7 @@ public abstract class ObjectUtils { * @return the new array (of the same component type; never {@code null}) * @since 6.0 */ - public static A[] addObjectToArray(A @Nullable [] array, @Nullable O obj, int position) { + public static @Nullable A[] addObjectToArray(A @Nullable [] array, @Nullable O obj, int position) { Class componentType = Object.class; if (array != null) { componentType = array.getClass().componentType(); @@ -276,7 +276,7 @@ public abstract class ObjectUtils { } int newArrayLength = (array != null ? array.length + 1 : 1); @SuppressWarnings("unchecked") - A[] newArray = (A[]) Array.newInstance(componentType, newArrayLength); + @Nullable A[] newArray = (A[]) Array.newInstance(componentType, newArrayLength); if (array != null) { System.arraycopy(array, 0, newArray, 0, position); System.arraycopy(array, position, newArray, position + 1, array.length - position); @@ -651,7 +651,7 @@ public abstract class ObjectUtils { * @param array the array to build a String representation for * @return a String representation of {@code array} */ - public static String nullSafeToString(Object @Nullable [] array) { + public static String nullSafeToString(@Nullable Object @Nullable [] array) { if (array == null) { return NULL_STRING; } diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java index 246960e1503..4accef59420 100644 --- a/spring-core/src/main/java/org/springframework/util/StringUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1038,12 +1038,12 @@ public abstract class StringUtils { * @param array the original {@code String} array (potentially empty) * @return the resulting array (of the same size) with trimmed elements */ - public static String[] trimArrayElements(String[] array) { + public static @Nullable String[] trimArrayElements(@Nullable String[] array) { if (ObjectUtils.isEmpty(array)) { return array; } - String[] result = new String[array.length]; + @Nullable String[] result = new String[array.length]; for (int i = 0; i < array.length; i++) { String element = array[i]; result[i] = (element != null ? element.trim() : null); diff --git a/spring-core/src/main/java/org/springframework/util/function/SingletonSupplier.java b/spring-core/src/main/java/org/springframework/util/function/SingletonSupplier.java index 270bc494b46..c5b2ef3752a 100644 --- a/spring-core/src/main/java/org/springframework/util/function/SingletonSupplier.java +++ b/spring-core/src/main/java/org/springframework/util/function/SingletonSupplier.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import java.util.function.Supplier; import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; import org.springframework.util.Assert; /** @@ -38,11 +39,11 @@ import org.springframework.util.Assert; * @since 5.1 * @param the type of results supplied by this supplier */ -public class SingletonSupplier implements Supplier { +public class SingletonSupplier implements Supplier<@Nullable T> { - private final @Nullable Supplier instanceSupplier; + private final @Nullable Supplier instanceSupplier; - private final @Nullable Supplier defaultSupplier; + private final @Nullable Supplier defaultSupplier; private volatile @Nullable T singletonInstance; @@ -58,7 +59,7 @@ public class SingletonSupplier implements Supplier { * @param instance the singleton instance (potentially {@code null}) * @param defaultSupplier the default supplier as a fallback */ - public SingletonSupplier(@Nullable T instance, Supplier defaultSupplier) { + public SingletonSupplier(@Nullable T instance, Supplier defaultSupplier) { this.instanceSupplier = null; this.defaultSupplier = defaultSupplier; this.singletonInstance = instance; @@ -70,17 +71,17 @@ public class SingletonSupplier implements Supplier { * @param instanceSupplier the immediate instance supplier * @param defaultSupplier the default supplier as a fallback */ - public SingletonSupplier(@Nullable Supplier instanceSupplier, Supplier defaultSupplier) { + public SingletonSupplier(@Nullable Supplier instanceSupplier, Supplier defaultSupplier) { this.instanceSupplier = instanceSupplier; this.defaultSupplier = defaultSupplier; } - private SingletonSupplier(Supplier supplier) { + private SingletonSupplier(Supplier supplier) { this.instanceSupplier = supplier; this.defaultSupplier = null; } - private SingletonSupplier(T singletonInstance) { + private SingletonSupplier(@Nullable T singletonInstance) { this.instanceSupplier = null; this.defaultSupplier = null; this.singletonInstance = singletonInstance; @@ -141,6 +142,7 @@ public class SingletonSupplier implements Supplier { * @param instance the singleton instance (potentially {@code null}) * @return the singleton supplier, or {@code null} if the instance was {@code null} */ + @Contract("null -> null; !null -> !null") public static @Nullable SingletonSupplier ofNullable(@Nullable T instance) { return (instance != null ? new SingletonSupplier<>(instance) : null); } @@ -159,7 +161,8 @@ public class SingletonSupplier implements Supplier { * @param supplier the instance supplier (potentially {@code null}) * @return the singleton supplier, or {@code null} if the instance supplier was {@code null} */ - public static @Nullable SingletonSupplier ofNullable(@Nullable Supplier supplier) { + @Contract("null -> null; !null -> !null") + public static @Nullable SingletonSupplier ofNullable(@Nullable Supplier supplier) { return (supplier != null ? new SingletonSupplier<>(supplier) : null); } diff --git a/spring-core/src/main/java/org/springframework/util/function/SupplierUtils.java b/spring-core/src/main/java/org/springframework/util/function/SupplierUtils.java index 1513ec2ca35..e268cf5783f 100644 --- a/spring-core/src/main/java/org/springframework/util/function/SupplierUtils.java +++ b/spring-core/src/main/java/org/springframework/util/function/SupplierUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ import java.util.function.Supplier; import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; + /** * Convenience utilities for {@link java.util.function.Supplier} handling. * @@ -35,6 +37,7 @@ public abstract class SupplierUtils { * @param supplier the supplier to resolve * @return the supplier's result, or {@code null} if none */ + @Contract("null -> null; !null -> !null") public static @Nullable T resolve(@Nullable Supplier supplier) { return (supplier != null ? supplier.get() : null); } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationsScannerTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationsScannerTests.java index 1c5e508eb3e..d5bbde3f5f8 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationsScannerTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationsScannerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Stream; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; @@ -469,13 +470,13 @@ class AnnotationsScannerTests { new AnnotationsProcessor() { @Override - public String doWithAggregate(Object context, int aggregateIndex) { + public @NonNull String doWithAggregate(Object context, int aggregateIndex) { return ""; } @Override - public String doWithAnnotations(Object context, int aggregateIndex, - @Nullable Object source, Annotation[] annotations) { + public @NonNull String doWithAnnotations(Object context, int aggregateIndex, + @Nullable Object source, @Nullable Annotation @Nullable [] annotations) { throw new IllegalStateException("Should not call"); } @@ -501,13 +502,13 @@ class AnnotationsScannerTests { new AnnotationsProcessor() { @Override - public String doWithAnnotations(Object context, int aggregateIndex, - @Nullable Object source, Annotation[] annotations) { + public @NonNull String doWithAnnotations(Object context, int aggregateIndex, + @Nullable Object source, @Nullable Annotation @Nullable [] annotations) { return "K"; } @Override - public String finish(@Nullable String result) { + public @NonNull String finish(@Nullable String result) { return "O" + result; }