From 380c9e318c1f501155c964a2f29262bf32ea1029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Mon, 13 Jan 2025 20:54:25 +0100 Subject: [PATCH] Specify generic type nullness in spring-web See gh-34140 --- .../springframework/http/RequestEntity.java | 10 +++++----- .../json/Jackson2ObjectMapperBuilder.java | 4 ++-- .../client/RestClientResponseException.java | 6 +++--- .../web/client/StatusHandler.java | 5 +++-- .../web/filter/FormContentFilter.java | 4 ++-- .../web/method/HandlerMethod.java | 15 ++++++++------ .../ExceptionHandlerMethodResolver.java | 7 ++++--- .../annotation/HandlerMethodValidator.java | 6 +++--- .../support/InvocableHandlerMethod.java | 20 ++++++++++--------- .../service/invoker/HttpServiceMethod.java | 12 +++++------ .../invoker/HttpServiceProxyFactory.java | 6 +++--- .../web/util/DefaultUriBuilderFactory.java | 3 +++ 12 files changed, 54 insertions(+), 44 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/RequestEntity.java b/spring-web/src/main/java/org/springframework/http/RequestEntity.java index bcbac6914a..faf6bb455f 100644 --- a/spring-web/src/main/java/org/springframework/http/RequestEntity.java +++ b/spring-web/src/main/java/org/springframework/http/RequestEntity.java @@ -738,14 +738,14 @@ public class RequestEntity extends HttpEntity { private final String uriTemplate; - private final Object @Nullable [] uriVarsArray; + private final @Nullable Object @Nullable [] uriVarsArray; - private final @Nullable Map uriVarsMap; + private final @Nullable Map uriVarsMap; UriTemplateRequestEntity( @Nullable T body, @Nullable HttpHeaders headers, @Nullable HttpMethod method, @Nullable Type type, String uriTemplate, - Object @Nullable [] uriVarsArray, @Nullable Map uriVarsMap) { + @Nullable Object @Nullable [] uriVarsArray, @Nullable Map uriVarsMap) { super(body, headers, method, null, type); this.uriTemplate = uriTemplate; @@ -757,11 +757,11 @@ public class RequestEntity extends HttpEntity { return this.uriTemplate; } - public Object @Nullable [] getVars() { + public @Nullable Object @Nullable [] getVars() { return this.uriVarsArray; } - public @Nullable Map getVarsMap() { + public @Nullable Map getVarsMap() { return this.uriVarsMap; } diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java index de5314f1f7..12c4bcbcca 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.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. @@ -260,7 +260,7 @@ public class Jackson2ObjectMapperBuilder { * @since 5.2.4 */ public Jackson2ObjectMapperBuilder annotationIntrospector( - Function pairingFunction) { + Function<@Nullable AnnotationIntrospector, @Nullable AnnotationIntrospector> pairingFunction) { this.annotationIntrospector = pairingFunction.apply(this.annotationIntrospector); return this; diff --git a/spring-web/src/main/java/org/springframework/web/client/RestClientResponseException.java b/spring-web/src/main/java/org/springframework/web/client/RestClientResponseException.java index 7f96b69f0b..d25f9743fd 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestClientResponseException.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestClientResponseException.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. @@ -56,7 +56,7 @@ public class RestClientResponseException extends RestClientException { private final @Nullable String responseCharset; - private transient @Nullable Function bodyConvertFunction; + private transient @Nullable Function bodyConvertFunction; /** @@ -200,7 +200,7 @@ public class RestClientResponseException extends RestClientException { * @param bodyConvertFunction the function to use * @since 6.0 */ - public void setBodyConvertFunction(Function bodyConvertFunction) { + public void setBodyConvertFunction(Function bodyConvertFunction) { this.bodyConvertFunction = bodyConvertFunction; } diff --git a/spring-web/src/main/java/org/springframework/web/client/StatusHandler.java b/spring-web/src/main/java/org/springframework/web/client/StatusHandler.java index 1d5cb0bc9f..2b11201dcc 100644 --- a/spring-web/src/main/java/org/springframework/web/client/StatusHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/StatusHandler.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. @@ -99,7 +99,8 @@ final class StatusHandler { }); } - private static Function initBodyConvertFunction(ClientHttpResponse response, byte[] body, List> messageConverters) { + @SuppressWarnings("NullAway") + private static Function initBodyConvertFunction(ClientHttpResponse response, byte[] body, List> messageConverters) { Assert.state(!CollectionUtils.isEmpty(messageConverters), "Expected message converters"); return resolvableType -> { try { diff --git a/spring-web/src/main/java/org/springframework/web/filter/FormContentFilter.java b/spring-web/src/main/java/org/springframework/web/filter/FormContentFilter.java index d4905d12de..e4af874a7f 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/FormContentFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/FormContentFilter.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. @@ -159,7 +159,7 @@ public class FormContentFilter extends OncePerRequestFilter { } @Override - public @Nullable String[] getParameterValues(String name) { + public String[] getParameterValues(String name) { String[] parameterValues = super.getParameterValues(name); List formParam = this.formParams.get(name); if (formParam == null) { diff --git a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java index 1dde8abf20..48f7f17803 100644 --- a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.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. @@ -357,7 +357,7 @@ public class HandlerMethod extends AnnotatedMethod { * beans, and others). {@code @Controller}'s that require proxying should prefer * class-based proxy mechanisms. */ - protected void assertTargetBean(Method method, Object targetBean, Object[] args) { + protected void assertTargetBean(Method method, Object targetBean, @Nullable Object[] args) { Class methodDeclaringClass = method.getDeclaringClass(); Class targetBeanClass = targetBean.getClass(); if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) { @@ -369,11 +369,14 @@ public class HandlerMethod extends AnnotatedMethod { } } - protected String formatInvokeError(String text, Object[] args) { + protected String formatInvokeError(String text, @Nullable Object[] args) { String formattedArgs = IntStream.range(0, args.length) - .mapToObj(i -> (args[i] != null ? - "[" + i + "] [type=" + args[i].getClass().getName() + "] [value=" + args[i] + "]" : - "[" + i + "] [null]")) + .mapToObj(i -> { + Object arg = args[i]; + return (arg != null ? + "[" + i + "] [type=" +arg.getClass().getName() + "] [value=" + arg + "]" : + "[" + i + "] [null]"); + }) .collect(Collectors.joining(",\n", " ", " ")); return text + "\n" + "Controller [" + getBeanType().getName() + "]\n" + diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.java index 0ea6217e08..5a70ec0021 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.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. @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.jspecify.annotations.Nullable; @@ -226,7 +227,7 @@ public class ExceptionHandlerMethodResolver { * Return the {@link Method} mapped to the given exception type, or * {@link #NO_MATCHING_EXCEPTION_HANDLER} if none. */ - private @Nullable ExceptionHandlerMappingInfo getMappedMethod(Class exceptionType, MediaType mediaType) { + private ExceptionHandlerMappingInfo getMappedMethod(Class exceptionType, MediaType mediaType) { List matches = new ArrayList<>(); for (ExceptionMapping mappingInfo : this.mappedMethods.keySet()) { if (mappingInfo.exceptionType().isAssignableFrom(exceptionType) && mappingInfo.mediaType().isCompatibleWith(mediaType)) { @@ -237,7 +238,7 @@ public class ExceptionHandlerMethodResolver { if (matches.size() > 1) { matches.sort(new ExceptionMapingComparator(exceptionType, mediaType)); } - return this.mappedMethods.get(matches.get(0)); + return Objects.requireNonNull(this.mappedMethods.get(matches.get(0))); } else { return NO_MATCHING_EXCEPTION_HANDLER; diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java index bd85cb07f9..aeb4975586 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.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. @@ -78,7 +78,7 @@ public final class HandlerMethodValidator implements MethodValidator { @Override public void applyArgumentValidation( Object target, Method method, MethodParameter @Nullable [] parameters, - Object[] arguments, Class[] groups) { + @Nullable Object[] arguments, Class[] groups) { MethodValidationResult result = validateArguments(target, method, parameters, arguments, groups); if (!result.hasErrors()) { @@ -110,7 +110,7 @@ public final class HandlerMethodValidator implements MethodValidator { @Override public MethodValidationResult validateArguments( Object target, Method method, MethodParameter @Nullable [] parameters, - Object[] arguments, Class[] groups) { + @Nullable Object[] arguments, Class[] groups) { return this.validationAdapter.validateArguments(target, method, parameters, arguments, groups); } diff --git a/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java index d49a6ae66b..bf17a15caf 100644 --- a/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.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. @@ -172,9 +172,9 @@ public class InvocableHandlerMethod extends HandlerMethod { * @see #doInvoke */ public @Nullable Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, - Object... providedArgs) throws Exception { + @Nullable Object... providedArgs) throws Exception { - Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); + @Nullable Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } @@ -200,15 +200,15 @@ public class InvocableHandlerMethod extends HandlerMethod { *

The resulting array will be passed into {@link #doInvoke}. * @since 5.1.2 */ - protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, - Object... providedArgs) throws Exception { + protected @Nullable Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, + @Nullable Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } - Object[] args = new Object[parameters.length]; + @Nullable Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); @@ -239,7 +239,7 @@ public class InvocableHandlerMethod extends HandlerMethod { /** * Invoke the handler method with the given argument values. */ - protected @Nullable Object doInvoke(Object... args) throws Exception { + protected @Nullable Object doInvoke(@Nullable Object... args) throws Exception { Method method = getBridgedMethod(); try { if (KotlinDetector.isKotlinReflectPresent()) { @@ -285,7 +285,7 @@ public class InvocableHandlerMethod extends HandlerMethod { * instead. * @since 6.0 */ - protected Object invokeSuspendingFunction(Method method, Object target, Object[] args) { + protected Object invokeSuspendingFunction(Method method, Object target, @Nullable Object[] args) { Object result = CoroutinesUtils.invokeSuspendingFunction(method, target, args); return (result instanceof Mono mono ? mono.handle(KotlinDelegate::handleResult) : result); } @@ -297,7 +297,9 @@ public class InvocableHandlerMethod extends HandlerMethod { private static class KotlinDelegate { @SuppressWarnings("DataFlowIssue") - public static @Nullable Object invokeFunction(Method method, Object target, Object[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { + public static @Nullable Object invokeFunction(Method method, Object target, @Nullable Object[] args) throws + InvocationTargetException, IllegalAccessException, NoSuchMethodException { + KFunction function = ReflectJvmMapping.getKotlinFunction(method); // For property accessors if (function == null) { diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceMethod.java b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceMethod.java index 9d9f0fd952..28400883ff 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceMethod.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceMethod.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. @@ -127,13 +127,13 @@ final class HttpServiceMethod { } - public @Nullable Object invoke(Object[] arguments) { + public @Nullable Object invoke(@Nullable Object[] arguments) { HttpRequestValues.Builder requestValues = this.requestValuesInitializer.initializeRequestValuesBuilder(); applyArguments(requestValues, arguments); return this.responseFunction.execute(requestValues.build()); } - private void applyArguments(HttpRequestValues.Builder requestValues, Object[] arguments) { + private void applyArguments(HttpRequestValues.Builder requestValues, @Nullable Object[] arguments) { Assert.isTrue(arguments.length == this.parameters.length, "Method argument mismatch"); for (int i = 0; i < arguments.length; i++) { Object value = arguments[i]; @@ -383,10 +383,10 @@ final class HttpServiceMethod { } private record ExchangeResponseFunction( - Function responseFunction) implements ResponseFunction { + Function responseFunction) implements ResponseFunction { @Override - public Object execute(HttpRequestValues requestValues) { + public @Nullable Object execute(HttpRequestValues requestValues) { return this.responseFunction.apply(requestValues); } @@ -403,7 +403,7 @@ final class HttpServiceMethod { MethodParameter param = new MethodParameter(method, -1).nestedIfOptional(); Class paramType = param.getNestedParameterType(); - Function responseFunction; + Function responseFunction; if (ClassUtils.isVoidType(paramType)) { responseFunction = requestValues -> { client.exchange(requestValues); diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java index d064cafd76..3bff90f04a 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.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. @@ -233,7 +233,7 @@ public final class HttpServiceProxyFactory { Method method = invocation.getMethod(); HttpServiceMethod httpServiceMethod = this.httpServiceMethods.get(method); if (httpServiceMethod != null) { - Object[] arguments = KotlinDetector.isSuspendingFunction(method) ? + @Nullable Object[] arguments = KotlinDetector.isSuspendingFunction(method) ? resolveCoroutinesArguments(invocation.getArguments()) : invocation.getArguments(); return httpServiceMethod.invoke(arguments); } @@ -246,7 +246,7 @@ public final class HttpServiceProxyFactory { throw new IllegalStateException("Unexpected method invocation: " + method); } - private static Object[] resolveCoroutinesArguments(Object[] args) { + private static Object[] resolveCoroutinesArguments(@Nullable Object[] args) { Object[] functionArgs = new Object[args.length - 1]; System.arraycopy(args, 0, functionArgs, 0, args.length - 1); return functionArgs; diff --git a/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java b/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java index 6302f8d176..1826b78b10 100644 --- a/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java +++ b/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java @@ -140,6 +140,7 @@ public class DefaultUriBuilderFactory implements UriBuilderFactory { * with a Map of variables. * @param defaultUriVariables default URI variable values */ + @SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1126 public void setDefaultUriVariables(@Nullable Map defaultUriVariables) { if (defaultUriVariables != null) { if (this.defaultUriVariables == null) { @@ -431,6 +432,7 @@ public class DefaultUriBuilderFactory implements UriBuilderFactory { } @Override + @SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1126 public URI build(Map uriVars) { if (!CollectionUtils.isEmpty(defaultUriVariables)) { Map map = new HashMap<>(defaultUriVariables.size() + uriVars.size()); @@ -446,6 +448,7 @@ public class DefaultUriBuilderFactory implements UriBuilderFactory { } @Override + @SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1126 public URI build(@Nullable Object... uriVars) { if (ObjectUtils.isEmpty(uriVars) && !CollectionUtils.isEmpty(defaultUriVariables)) { return build(Collections.emptyMap());