From c531a8a7058d24a110b312cd9a7a8bcf306845af Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 19 Mar 2024 09:58:44 +0100 Subject: [PATCH] Nullability refinements and related polishing See gh-32475 --- .../springframework/aop/support/AopUtils.java | 4 +- .../AbstractNestablePropertyAccessor.java | 12 ++-- .../groovy/GroovyBeanDefinitionReader.java | 19 ++++--- .../groovy/GroovyBeanDefinitionWrapper.java | 16 +++--- .../factory/support/RootBeanDefinition.java | 4 +- .../DefaultBeanDefinitionDocumentReader.java | 9 +-- .../cache/interceptor/CacheAspectSupport.java | 10 +++- .../cache/interceptor/CacheInterceptor.java | 5 +- .../context/aot/AbstractAotProcessor.java | 10 ++-- .../ApplicationListenerMethodAdapter.java | 6 +- .../jmx/access/MBeanProxyFactoryBean.java | 13 +++-- .../validation/DataBinder.java | 4 +- .../MethodValidationInterceptor.java | 8 +-- .../aot/generate/DefaultMethodReference.java | 17 +++--- .../predicate/ReflectionHintsPredicates.java | 53 +++++++++++------- .../springframework/core/CoroutinesUtils.java | 9 ++- .../core/annotation/AnnotationUtils.java | 5 +- .../MergedAnnotationsCollection.java | 28 +++++----- .../support/StringToRegexConverter.java | 2 +- .../core/io/buffer/DataBufferUtils.java | 12 ++-- .../core/io/buffer/OutputStreamPublisher.java | 27 ++++----- .../org/springframework/util/ClassUtils.java | 2 +- .../util/SerializationUtils.java | 6 +- .../generate/DefaultMethodReferenceTests.java | 4 +- .../spel/ast/ConstructorReference.java | 4 +- .../expression/spel/ast/Indexer.java | 8 ++- .../expression/spel/standard/Token.java | 2 +- .../spel/support/ReflectionHelper.java | 7 ++- .../metadata/CallMetaDataProviderFactory.java | 6 +- .../core/metadata/TableMetaDataContext.java | 2 +- .../embedded/EmbeddedDatabaseFactoryBean.java | 7 ++- .../reactive/ChannelSendOperator.java | 7 ++- .../reactive/InvocableHandlerMethod.java | 7 +-- .../HibernateTransactionManager.java | 7 ++- .../orm/jpa/JpaTransactionManager.java | 7 ++- .../test/context/aot/GeneratedMapUtils.java | 6 +- .../aot/TestContextGenerationContext.java | 9 ++- ...ervationRegistryTestExecutionListener.java | 4 +- .../support/TestPropertySourceAttributes.java | 4 +- ...ultClientRequestObservationConvention.java | 8 ++- .../ReactorNetty2ClientHttpResponse.java | 17 ++++-- .../server/reactive/ChannelSendOperator.java | 5 +- .../context/request/ServletWebRequest.java | 56 +++++++++---------- .../async/CallableInterceptorChain.java | 5 +- .../context/request/async/DeferredResult.java | 8 +-- .../reactive/ServerHttpObservationFilter.java | 19 ++++--- ...tractNamedValueMethodArgumentResolver.java | 6 +- .../server/adapter/HttpWebHandlerAdapter.java | 18 +++--- .../web/util/pattern/RegexPathElement.java | 8 +-- ...ultClientRequestObservationConvention.java | 7 ++- .../resource/ResourceUrlProvider.java | 4 +- .../condition/ConsumesRequestCondition.java | 6 +- .../condition/ProducesRequestCondition.java | 12 ++-- .../AbstractNamedValueArgumentResolver.java | 6 +- .../method/annotation/ModelInitializer.java | 12 ++-- .../ResponseEntityResultHandler.java | 3 +- .../condition/ConsumesRequestCondition.java | 4 +- .../condition/ProducesRequestCondition.java | 8 +-- 58 files changed, 327 insertions(+), 257 deletions(-) diff --git a/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java b/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java index 526a3a0032..d7383e79ee 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java @@ -364,18 +364,18 @@ public abstract class AopUtils { } } + /** * Inner class to avoid a hard dependency on Kotlin at runtime. */ private static class KotlinDelegate { - public static Publisher invokeSuspendingFunction(Method method, Object target, Object... args) { + public static Publisher invokeSuspendingFunction(Method method, @Nullable Object target, Object... args) { Continuation continuation = (Continuation) args[args.length -1]; Assert.state(continuation != null, "No Continuation available"); CoroutineContext context = continuation.getContext().minusKey(Job.Key); return CoroutinesUtils.invokeSuspendingFunction(context, method, target, args); } - } } diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java index a0fcaaf050..04fc76399a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -845,8 +845,10 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA * @return the PropertyAccessor instance, either cached or newly created */ private AbstractNestablePropertyAccessor getNestedPropertyAccessor(String nestedProperty) { - if (this.nestedPropertyAccessors == null) { - this.nestedPropertyAccessors = new HashMap<>(); + Map nestedAccessors = this.nestedPropertyAccessors; + if (nestedAccessors == null) { + nestedAccessors = new HashMap<>(); + this.nestedPropertyAccessors = nestedAccessors; } // Get value of bean property. PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty); @@ -862,7 +864,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA } // Lookup cached sub-PropertyAccessor, create new one if not found. - AbstractNestablePropertyAccessor nestedPa = this.nestedPropertyAccessors.get(canonicalName); + AbstractNestablePropertyAccessor nestedPa = nestedAccessors.get(canonicalName); if (nestedPa == null || nestedPa.getWrappedInstance() != ObjectUtils.unwrapOptional(value)) { if (logger.isTraceEnabled()) { logger.trace("Creating new nested " + getClass().getSimpleName() + " for property '" + canonicalName + "'"); @@ -871,7 +873,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA // Inherit all type-specific PropertyEditors. copyDefaultEditorsTo(nestedPa); copyCustomEditorsTo(nestedPa, canonicalName); - this.nestedPropertyAccessors.put(canonicalName, nestedPa); + nestedAccessors.put(canonicalName, nestedPa); } else { if (logger.isTraceEnabled()) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java index 363ec52896..b098663f65 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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,6 +54,7 @@ import org.springframework.core.io.DescriptiveResource; import org.springframework.core.io.Resource; import org.springframework.core.io.support.EncodedResource; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -250,6 +251,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp @SuppressWarnings("serial") Closure beans = new Closure<>(this) { @Override + @Nullable public Object call(Object... args) { invokeBeanDefiningClosure((Closure) args[0]); return null; @@ -425,6 +427,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp private boolean addDeferredProperty(String property, Object newValue) { if (newValue instanceof List || newValue instanceof Map) { + Assert.state(this.currentBeanDefinition != null, "No current bean definition set"); this.deferredProperties.put(this.currentBeanDefinition.getBeanName() + '.' + property, new DeferredProperty(this.currentBeanDefinition, property, newValue)); return true; @@ -640,6 +643,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp this.currentBeanDefinition = current; } } + Assert.state(this.currentBeanDefinition != null, "No current bean definition set"); this.currentBeanDefinition.addProperty(name, value); } @@ -654,6 +658,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp * */ @Override + @Nullable public Object getProperty(String name) { Binding binding = getBinding(); if (binding != null && binding.hasVariable(name)) { @@ -727,9 +732,10 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp private final String name; + @Nullable public Object value; - public DeferredProperty(GroovyBeanDefinitionWrapper beanDefinition, String name, Object value) { + public DeferredProperty(GroovyBeanDefinitionWrapper beanDefinition, String name, @Nullable Object value) { this.beanDefinition = beanDefinition; this.name = name; this.value = value; @@ -762,6 +768,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp } @Override + @Nullable public Object getProperty(String property) { if (property.equals("beanName")) { return getBeanName(); @@ -769,13 +776,10 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp else if (property.equals("source")) { return getSource(); } - else if (this.beanDefinition != null) { + else { return new GroovyPropertyValue( property, this.beanDefinition.getBeanDefinition().getPropertyValues().get(property)); } - else { - return this.metaClass.getProperty(this, property); - } } @Override @@ -804,9 +808,10 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp private final String propertyName; + @Nullable private final Object propertyValue; - public GroovyPropertyValue(String propertyName, Object propertyValue) { + public GroovyPropertyValue(String propertyName, @Nullable Object propertyValue) { this.propertyName = propertyName; this.propertyValue = propertyValue; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionWrapper.java b/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionWrapper.java index e52922da03..895646d9da 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionWrapper.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -84,7 +84,7 @@ class GroovyBeanDefinitionWrapper extends GroovyObjectSupport { this(beanName, clazz, null); } - GroovyBeanDefinitionWrapper(@Nullable String beanName, Class clazz, @Nullable Collection constructorArgs) { + GroovyBeanDefinitionWrapper(@Nullable String beanName, @Nullable Class clazz, @Nullable Collection constructorArgs) { this.beanName = beanName; this.clazz = clazz; this.constructorArgs = constructorArgs; @@ -130,11 +130,12 @@ class GroovyBeanDefinitionWrapper extends GroovyObjectSupport { } BeanDefinitionHolder getBeanDefinitionHolder() { - return new BeanDefinitionHolder(getBeanDefinition(), getBeanName()); + Assert.state(this.beanName != null, "Bean name must be set"); + return new BeanDefinitionHolder(getBeanDefinition(), this.beanName); } - void setParent(Object obj) { - Assert.notNull(obj, "Parent bean cannot be set to a null runtime bean reference."); + void setParent(@Nullable Object obj) { + Assert.notNull(obj, "Parent bean cannot be set to a null runtime bean reference"); if (obj instanceof String name) { this.parentName = name; } @@ -148,7 +149,7 @@ class GroovyBeanDefinitionWrapper extends GroovyObjectSupport { getBeanDefinition().setAbstract(false); } - GroovyBeanDefinitionWrapper addProperty(String propertyName, Object propertyValue) { + GroovyBeanDefinitionWrapper addProperty(String propertyName, @Nullable Object propertyValue) { if (propertyValue instanceof GroovyBeanDefinitionWrapper wrapper) { propertyValue = wrapper.getBeanDefinition(); } @@ -158,6 +159,7 @@ class GroovyBeanDefinitionWrapper extends GroovyObjectSupport { @Override + @Nullable public Object getProperty(String property) { Assert.state(this.definitionWrapper != null, "BeanDefinition wrapper not initialized"); if (this.definitionWrapper.isReadableProperty(property)) { @@ -170,7 +172,7 @@ class GroovyBeanDefinitionWrapper extends GroovyObjectSupport { } @Override - public void setProperty(String property, Object newValue) { + public void setProperty(String property, @Nullable Object newValue) { if (PARENT.equals(property)) { setParent(newValue); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index c3ac193a77..ccdc1e1454 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -638,7 +638,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition { } } - private static boolean hasAnyExternallyManagedMethod(Set candidates, String methodName) { + private static boolean hasAnyExternallyManagedMethod(@Nullable Set candidates, String methodName) { if (candidates != null) { for (String candidate : candidates) { int indexOfDot = candidate.lastIndexOf('.'); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java index b8e2935a9c..0e556b94bc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2024 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. @@ -126,9 +126,10 @@ public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocume // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. BeanDefinitionParserDelegate parent = this.delegate; - this.delegate = createDelegate(getReaderContext(), root, parent); + BeanDefinitionParserDelegate current = createDelegate(getReaderContext(), root, parent); + this.delegate = current; - if (this.delegate.isDefaultNamespace(root)) { + if (current.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( @@ -146,7 +147,7 @@ public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocume } preProcessXml(root); - parseBeanDefinitions(root, this.delegate); + parseBeanDefinitions(root, current); postProcessXml(root); this.delegate = parent; diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java index e438872e75..75f2293e7e 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -1096,7 +1096,13 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker } } if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isSuspendingFunction(method)) { - return Mono.fromFuture(cache.retrieve(key, () -> ((Mono) invokeOperation(invoker)).toFuture())); + return Mono.fromFuture(cache.retrieve(key, () -> { + Mono mono = ((Mono) invokeOperation(invoker)); + if (mono == null) { + mono = Mono.empty(); + } + return mono.toFuture(); + })); } return NOT_HANDLED; } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java index a0975f05aa..cbe839a247 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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,12 +78,13 @@ public class CacheInterceptor extends CacheAspectSupport implements MethodInterc } } + /** * Inner class to avoid a hard dependency on Kotlin at runtime. */ private static class KotlinDelegate { - public static Publisher invokeSuspendingFunction(Method method, Object target, Object... args) { + public static Publisher invokeSuspendingFunction(Method method, @Nullable Object target, Object... args) { Continuation continuation = (Continuation) args[args.length - 1]; CoroutineContext coroutineContext = continuation.getContext().minusKey(Job.Key); return CoroutinesUtils.invokeSuspendingFunction(coroutineContext, method, target, args); diff --git a/spring-context/src/main/java/org/springframework/context/aot/AbstractAotProcessor.java b/spring-context/src/main/java/org/springframework/context/aot/AbstractAotProcessor.java index 9acfa463e2..b342e4d789 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/AbstractAotProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/aot/AbstractAotProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -257,6 +257,7 @@ public abstract class AbstractAotProcessor { * @return this builder for method chaining */ public Builder groupId(String groupId) { + Assert.hasText(groupId, "'groupId' must not be empty"); this.groupId = groupId; return this; } @@ -268,6 +269,7 @@ public abstract class AbstractAotProcessor { * @return this builder for method chaining */ public Builder artifactId(String artifactId) { + Assert.hasText(artifactId, "'artifactId' must not be empty"); this.artifactId = artifactId; return this; } @@ -279,14 +281,12 @@ public abstract class AbstractAotProcessor { Assert.notNull(this.sourceOutput, "'sourceOutput' must not be null"); Assert.notNull(this.resourceOutput, "'resourceOutput' must not be null"); Assert.notNull(this.classOutput, "'classOutput' must not be null"); - Assert.hasText(this.groupId, "'groupId' must not be null or empty"); - Assert.hasText(this.artifactId, "'artifactId' must not be null or empty"); + Assert.notNull(this.groupId, "'groupId' must not be null"); + Assert.notNull(this.artifactId, "'artifactId' must not be null"); return new Settings(this.sourceOutput, this.resourceOutput, this.classOutput, this.groupId, this.artifactId); } - } - } } diff --git a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java index d60615b7af..9a2c88ced1 100644 --- a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java +++ b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -318,8 +318,8 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe } } - private void publishEvents(Object result) { - if (result.getClass().isArray()) { + private void publishEvents(@Nullable Object result) { + if (result != null && result.getClass().isArray()) { Object[] events = ObjectUtils.toObjectArray(result); for (Object event : events) { publishEvent(event); diff --git a/spring-context/src/main/java/org/springframework/jmx/access/MBeanProxyFactoryBean.java b/spring-context/src/main/java/org/springframework/jmx/access/MBeanProxyFactoryBean.java index f55de8e37b..0acc722b26 100644 --- a/spring-context/src/main/java/org/springframework/jmx/access/MBeanProxyFactoryBean.java +++ b/spring-context/src/main/java/org/springframework/jmx/access/MBeanProxyFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2024 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. @@ -83,18 +83,21 @@ public class MBeanProxyFactoryBean extends MBeanClientInterceptor public void afterPropertiesSet() throws MBeanServerNotFoundException, MBeanInfoRetrievalException { super.afterPropertiesSet(); + Class interfaceToUse; if (this.proxyInterface == null) { - this.proxyInterface = getManagementInterface(); - if (this.proxyInterface == null) { + interfaceToUse = getManagementInterface(); + if (interfaceToUse == null) { throw new IllegalArgumentException("Property 'proxyInterface' or 'managementInterface' is required"); } + this.proxyInterface = interfaceToUse; } else { + interfaceToUse = this.proxyInterface; if (getManagementInterface() == null) { - setManagementInterface(this.proxyInterface); + setManagementInterface(interfaceToUse); } } - this.mbeanProxy = new ProxyFactory(this.proxyInterface, this).getProxy(this.beanClassLoader); + this.mbeanProxy = new ProxyFactory(interfaceToUse, this).getProxy(this.beanClassLoader); } diff --git a/spring-context/src/main/java/org/springframework/validation/DataBinder.java b/spring-context/src/main/java/org/springframework/validation/DataBinder.java index 12b222d335..c7af62b501 100644 --- a/spring-context/src/main/java/org/springframework/validation/DataBinder.java +++ b/spring-context/src/main/java/org/springframework/validation/DataBinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -1035,7 +1035,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { Class constructorClass, String nestedPath, String name, @Nullable Object value) { Object[] hints = null; - if (this.targetType.getSource() instanceof MethodParameter parameter) { + if (this.targetType != null && this.targetType.getSource() instanceof MethodParameter parameter) { for (Annotation ann : parameter.getParameterAnnotations()) { hints = ValidationAnnotationUtils.determineValidationHints(ann); if (hints != null) { diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java index 16b87a1d46..fb791ae5f3 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -79,8 +79,8 @@ import org.springframework.validation.method.ParameterValidationResult; */ public class MethodValidationInterceptor implements MethodInterceptor { - private static final boolean REACTOR_PRESENT = - ClassUtils.isPresent("reactor.core.publisher.Mono", MethodValidationInterceptor.class.getClassLoader()); + private static final boolean reactorPresent = ClassUtils.isPresent( + "reactor.core.publisher.Mono", MethodValidationInterceptor.class.getClassLoader()); private final MethodValidationAdapter validationAdapter; @@ -153,7 +153,7 @@ public class MethodValidationInterceptor implements MethodInterceptor { Object[] arguments = invocation.getArguments(); Class[] groups = determineValidationGroups(invocation); - if (REACTOR_PRESENT) { + if (reactorPresent) { arguments = ReactorValidationHelper.insertAsyncValidation( this.validationAdapter.getSpringValidatorAdapter(), this.adaptViolations, target, method, arguments); diff --git a/spring-core/src/main/java/org/springframework/aot/generate/DefaultMethodReference.java b/spring-core/src/main/java/org/springframework/aot/generate/DefaultMethodReference.java index ca512fb27d..3018c6407c 100644 --- a/spring-core/src/main/java/org/springframework/aot/generate/DefaultMethodReference.java +++ b/spring-core/src/main/java/org/springframework/aot/generate/DefaultMethodReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -53,7 +53,7 @@ public class DefaultMethodReference implements MethodReference { public CodeBlock toCodeBlock() { String methodName = this.method.name; if (isStatic()) { - Assert.state(this.declaringClass != null, "static method reference must define a declaring class"); + Assert.state(this.declaringClass != null, "Static method reference must define a declaring class"); return CodeBlock.of("$T::$L", this.declaringClass, methodName); } else { @@ -64,11 +64,12 @@ public class DefaultMethodReference implements MethodReference { @Override public CodeBlock toInvokeCodeBlock(ArgumentCodeGenerator argumentCodeGenerator, @Nullable ClassName targetClassName) { + String methodName = this.method.name; CodeBlock.Builder code = CodeBlock.builder(); if (isStatic()) { - Assert.state(this.declaringClass != null, "static method reference must define a declaring class"); - if (isSameDeclaringClass(targetClassName)) { + Assert.state(this.declaringClass != null, "Static method reference must define a declaring class"); + if (this.declaringClass.equals(targetClassName)) { code.add("$L", methodName); } else { @@ -76,7 +77,7 @@ public class DefaultMethodReference implements MethodReference { } } else { - if (!isSameDeclaringClass(targetClassName)) { + if (this.declaringClass != null && !this.declaringClass.equals(targetClassName)) { code.add(instantiateDeclaringClass(this.declaringClass)); } code.add("$L", methodName); @@ -117,10 +118,6 @@ public class DefaultMethodReference implements MethodReference { return this.method.modifiers.contains(Modifier.STATIC); } - private boolean isSameDeclaringClass(ClassName declaringClass) { - return this.declaringClass == null || this.declaringClass.equals(declaringClass); - } - @Override public String toString() { String methodName = this.method.name; @@ -128,7 +125,7 @@ public class DefaultMethodReference implements MethodReference { return this.declaringClass + "::" + methodName; } else { - return ((this.declaringClass != null) ? + return (this.declaringClass != null ? "<" + this.declaringClass + ">" : "") + "::" + methodName; } } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java index 01d25229a0..a9f87de77a 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -194,6 +194,7 @@ public class ReflectionHintsPredicates { return new FieldHintPredicate(field); } + public static class TypeHintPredicate implements Predicate { private final TypeReference type; @@ -212,7 +213,6 @@ public class ReflectionHintsPredicates { return getTypeHint(hints) != null; } - /** * Refine the current predicate to only match if the given {@link MemberCategory} is present. * @param memberCategory the member category @@ -220,7 +220,10 @@ public class ReflectionHintsPredicates { */ public Predicate withMemberCategory(MemberCategory memberCategory) { Assert.notNull(memberCategory, "'memberCategory' must not be null"); - return this.and(hints -> getTypeHint(hints).getMemberCategories().contains(memberCategory)); + return and(hints -> { + TypeHint hint = getTypeHint(hints); + return (hint != null && hint.getMemberCategories().contains(memberCategory)); + }); } /** @@ -230,7 +233,10 @@ public class ReflectionHintsPredicates { */ public Predicate withMemberCategories(MemberCategory... memberCategories) { Assert.notEmpty(memberCategories, "'memberCategories' must not be empty"); - return this.and(hints -> getTypeHint(hints).getMemberCategories().containsAll(Arrays.asList(memberCategories))); + return and(hints -> { + TypeHint hint = getTypeHint(hints); + return (hint != null && hint.getMemberCategories().containsAll(Arrays.asList(memberCategories))); + }); } /** @@ -240,12 +246,15 @@ public class ReflectionHintsPredicates { */ public Predicate withAnyMemberCategory(MemberCategory... memberCategories) { Assert.notEmpty(memberCategories, "'memberCategories' must not be empty"); - return this.and(hints -> Arrays.stream(memberCategories) - .anyMatch(memberCategory -> getTypeHint(hints).getMemberCategories().contains(memberCategory))); + return and(hints -> { + TypeHint hint = getTypeHint(hints); + return (hint != null && Arrays.stream(memberCategories) + .anyMatch(memberCategory -> hint.getMemberCategories().contains(memberCategory))); + }); } - } + public abstract static class ExecutableHintPredicate implements Predicate { protected final T executable; @@ -289,6 +298,7 @@ public class ReflectionHintsPredicates { } } + public static class ConstructorHintPredicate extends ExecutableHintPredicate> { ConstructorHintPredicate(Constructor constructor) { @@ -322,15 +332,17 @@ public class ReflectionHintsPredicates { @Override Predicate exactMatch() { - return hints -> (hints.reflection().getTypeHint(this.executable.getDeclaringClass()) != null) && - hints.reflection().getTypeHint(this.executable.getDeclaringClass()).constructors().anyMatch(executableHint -> { - List parameters = TypeReference.listOf(this.executable.getParameterTypes()); - return includes(executableHint, "", parameters, this.executableMode); - }); + return hints -> { + TypeHint hint = hints.reflection().getTypeHint(this.executable.getDeclaringClass()); + return (hint != null && hint.constructors().anyMatch(executableHint -> { + List parameters = TypeReference.listOf(this.executable.getParameterTypes()); + return includes(executableHint, "", parameters, this.executableMode); + })); + }; } - } + public static class MethodHintPredicate extends ExecutableHintPredicate { MethodHintPredicate(Method method) { @@ -367,15 +379,17 @@ public class ReflectionHintsPredicates { @Override Predicate exactMatch() { - return hints -> (hints.reflection().getTypeHint(this.executable.getDeclaringClass()) != null) && - hints.reflection().getTypeHint(this.executable.getDeclaringClass()).methods().anyMatch(executableHint -> { - List parameters = TypeReference.listOf(this.executable.getParameterTypes()); - return includes(executableHint, this.executable.getName(), parameters, this.executableMode); - }); + return hints -> { + TypeHint hint = hints.reflection().getTypeHint(this.executable.getDeclaringClass()); + return (hint != null && hint.methods().anyMatch(executableHint -> { + List parameters = TypeReference.listOf(this.executable.getParameterTypes()); + return includes(executableHint, this.executable.getName(), parameters, this.executableMode); + })); + }; } - } + public static class FieldHintPredicate implements Predicate { private final Field field; @@ -406,7 +420,6 @@ public class ReflectionHintsPredicates { return typeHint.fields().anyMatch(fieldHint -> this.field.getName().equals(fieldHint.getName())); } - } } diff --git a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java index 3b69ff38e4..8ba870d07a 100644 --- a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java +++ b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java @@ -46,6 +46,7 @@ import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -64,6 +65,7 @@ public abstract class CoroutinesUtils { private static final KType publisherType = KClassifiers.getStarProjectedType(JvmClassMappingKt.getKotlinClass(Publisher.class)); + /** * Convert a {@link Deferred} instance to a {@link Mono}. */ @@ -109,9 +111,10 @@ public abstract class CoroutinesUtils { * @since 6.0 */ @SuppressWarnings({"deprecation", "DataFlowIssue"}) - public static Publisher invokeSuspendingFunction(CoroutineContext context, Method method, Object target, - Object... args) { - Assert.isTrue(KotlinDetector.isSuspendingFunction(method), "'method' must be a suspending function"); + public static Publisher invokeSuspendingFunction( + CoroutineContext context, Method method, @Nullable Object target, Object... args) { + + Assert.isTrue(KotlinDetector.isSuspendingFunction(method), "Method must be a suspending function"); KFunction function = Objects.requireNonNull(ReflectJvmMapping.getKotlinFunction(method)); if (method.isAccessible() && !KCallablesJvm.isAccessible(function)) { KCallablesJvm.setAccessible(function, true); 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 02cda09089..34a59d6720 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 @@ -650,11 +650,10 @@ public abstract class AnnotationUtils { return null; } - return (Class) MergedAnnotations.from(clazz, SearchStrategy.SUPERCLASS) - .stream() + MergedAnnotation merged = MergedAnnotations.from(clazz, SearchStrategy.SUPERCLASS).stream() .filter(MergedAnnotationPredicates.typeIn(annotationTypes).and(MergedAnnotation::isDirectlyPresent)) - .map(MergedAnnotation::getSource) .findFirst().orElse(null); + return (merged != null && merged.getSource() instanceof Class sourceClass ? sourceClass : null); } /** diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationsCollection.java b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationsCollection.java index 0d2e8372d0..01c0bafd1b 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationsCollection.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationsCollection.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2024 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. @@ -167,19 +167,21 @@ final class MergedAnnotationsCollection implements MergedAnnotations { MergedAnnotation result = null; for (int i = 0; i < this.annotations.length; i++) { MergedAnnotation root = this.annotations[i]; - AnnotationTypeMappings mappings = this.mappings[i]; - for (int mappingIndex = 0; mappingIndex < mappings.size(); mappingIndex++) { - AnnotationTypeMapping mapping = mappings.get(mappingIndex); - if (!isMappingForType(mapping, requiredType)) { - continue; - } - MergedAnnotation candidate = (mappingIndex == 0 ? (MergedAnnotation) root : - TypeMappedAnnotation.createIfPossible(mapping, root, IntrospectionFailureLogger.INFO)); - if (candidate != null && (predicate == null || predicate.test(candidate))) { - if (selector.isBestCandidate(candidate)) { - return candidate; + if (root != null) { + AnnotationTypeMappings mappings = this.mappings[i]; + for (int mappingIndex = 0; mappingIndex < mappings.size(); mappingIndex++) { + AnnotationTypeMapping mapping = mappings.get(mappingIndex); + if (!isMappingForType(mapping, requiredType)) { + continue; + } + MergedAnnotation candidate = (mappingIndex == 0 ? (MergedAnnotation) root : + TypeMappedAnnotation.createIfPossible(mapping, root, IntrospectionFailureLogger.INFO)); + if (candidate != null && (predicate == null || predicate.test(candidate))) { + if (selector.isBestCandidate(candidate)) { + return candidate; + } + result = (result != null ? selector.select(result, candidate) : candidate); } - result = (result != null ? selector.select(result, candidate) : candidate); } } } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToRegexConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToRegexConverter.java index 93d9631424..10a1b93d19 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToRegexConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToRegexConverter.java @@ -28,7 +28,7 @@ import org.springframework.lang.Nullable; * @author Sebastien Deleuze * @since 6.1 */ -class StringToRegexConverter implements Converter { +final class StringToRegexConverter implements Converter { @Override @Nullable diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java index b5ed08ad60..caee77b2c0 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java @@ -1113,13 +1113,13 @@ public abstract class DataBufferUtils { } @Override - public void failed(Throwable exc, Attachment attachment) { + public void failed(Throwable ex, Attachment attachment) { attachment.iterator().close(); release(attachment.dataBuffer()); closeChannel(this.channel); this.state.set(State.DISPOSED); - this.sink.error(exc); + this.sink.error(ex); } private enum State { @@ -1178,7 +1178,6 @@ public abstract class DataBufferUtils { public Context currentContext() { return Context.of(this.sink.contextView()); } - } @@ -1273,13 +1272,13 @@ public abstract class DataBufferUtils { } @Override - public void failed(Throwable exc, Attachment attachment) { + public void failed(Throwable ex, Attachment attachment) { attachment.iterator().close(); this.sink.next(attachment.dataBuffer()); this.writing.set(false); - this.sink.error(exc); + this.sink.error(ex); } @Override @@ -1288,9 +1287,6 @@ public abstract class DataBufferUtils { } private record Attachment(ByteBuffer byteBuffer, DataBuffer dataBuffer, DataBuffer.ByteBufferIterator iterator) {} - - } - } diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/OutputStreamPublisher.java b/spring-core/src/main/java/org/springframework/core/io/buffer/OutputStreamPublisher.java index 17e5d2cc29..ef4eee3624 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/OutputStreamPublisher.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/OutputStreamPublisher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -178,13 +178,14 @@ final class OutputStreamPublisher implements Publisher { if (isCancelled(previousState)) { return; } - if (isTerminated(previousState)) { // failure due to illegal requestN - this.actual.onError(this.error); - return; + Throwable error = this.error; + if (error != null) { + this.actual.onError(error); + return; + } } - this.actual.onError(ex); return; } @@ -193,13 +194,14 @@ final class OutputStreamPublisher implements Publisher { if (isCancelled(previousState)) { return; } - if (isTerminated(previousState)) { // failure due to illegal requestN - this.actual.onError(this.error); - return; + Throwable error = this.error; + if (error != null) { + this.actual.onError(error); + return; + } } - this.actual.onComplete(); } @@ -209,16 +211,13 @@ final class OutputStreamPublisher implements Publisher { if (n <= 0) { this.error = new IllegalArgumentException("request should be a positive number"); long previousState = tryTerminate(); - if (isTerminated(previousState) || isCancelled(previousState)) { return; } - if (previousState > 0) { // error should eventually be observed and propagated return; } - // resume parked thread, so it can observe error and propagate it resume(); return; @@ -276,11 +275,9 @@ final class OutputStreamPublisher implements Publisher { private long tryCancel() { while (true) { long r = this.requested.get(); - if (isCancelled(r)) { return r; } - if (this.requested.compareAndSet(r, Long.MIN_VALUE)) { return r; } @@ -290,11 +287,9 @@ final class OutputStreamPublisher implements Publisher { private long tryTerminate() { while (true) { long r = this.requested.get(); - if (isCancelled(r) || isTerminated(r)) { return r; } - if (this.requested.compareAndSet(r, Long.MIN_VALUE | Long.MAX_VALUE)) { return r; } diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java index 8e68e6af83..88e4e8b4b0 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -557,7 +557,7 @@ public abstract class ClassUtils { * @see Void * @see Void#TYPE */ - public static boolean isVoidType(Class type) { + public static boolean isVoidType(@Nullable Class type) { return (type == void.class || type == Void.class); } diff --git a/spring-core/src/main/java/org/springframework/util/SerializationUtils.java b/spring-core/src/main/java/org/springframework/util/SerializationUtils.java index 22f18b7af0..1eb905a785 100644 --- a/spring-core/src/main/java/org/springframework/util/SerializationUtils.java +++ b/spring-core/src/main/java/org/springframework/util/SerializationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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,9 @@ public abstract class SerializationUtils { */ @SuppressWarnings("unchecked") public static T clone(T object) { - return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object)); + Object result = SerializationUtils.deserialize(SerializationUtils.serialize(object)); + Assert.state(result != null, "Deserialized object must not be null"); + return (T) result; } } diff --git a/spring-core/src/test/java/org/springframework/aot/generate/DefaultMethodReferenceTests.java b/spring-core/src/test/java/org/springframework/aot/generate/DefaultMethodReferenceTests.java index d5c54e75ab..bc62da2b9b 100644 --- a/spring-core/src/test/java/org/springframework/aot/generate/DefaultMethodReferenceTests.java +++ b/spring-core/src/test/java/org/springframework/aot/generate/DefaultMethodReferenceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -88,7 +88,7 @@ class DefaultMethodReferenceTests { MethodSpec method = createTestMethod("methodName", new TypeName[0], Modifier.STATIC); MethodReference methodReference = new DefaultMethodReference(method, null); assertThatIllegalStateException().isThrownBy(methodReference::toCodeBlock) - .withMessage("static method reference must define a declaring class"); + .withMessage("Static method reference must define a declaring class"); } @Test diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java index 7b3a735968..d5f071820c 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -230,7 +230,7 @@ public class ConstructorReference extends SpelNodeImpl { InlineList initializer = (InlineList) getChild(1); sb.append("[] ").append(initializer.toStringAST()); } - else { + else if (this.dimensions != null) { // new int[3], new java.lang.String[3][4], etc. for (SpelNodeImpl dimension : this.dimensions) { sb.append('[').append(dimension.toStringAST()).append(']'); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index e0bc8be533..2f6f8a922b 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -222,7 +222,9 @@ public class Indexer extends SpelNodeImpl { } if (this.indexedType == IndexedType.ARRAY) { - int insn = switch (this.exitTypeDescriptor) { + String exitTypeDescriptor = this.exitTypeDescriptor; + Assert.state(exitTypeDescriptor != null, "Array not compilable without descriptor"); + int insn = switch (exitTypeDescriptor) { case "D" -> { mv.visitTypeInsn(CHECKCAST, "[D"); yield DALOAD; @@ -258,8 +260,8 @@ public class Indexer extends SpelNodeImpl { yield CALOAD; } default -> { - mv.visitTypeInsn(CHECKCAST, "["+ this.exitTypeDescriptor + - (CodeFlow.isPrimitiveArray(this.exitTypeDescriptor) ? "" : ";")); + mv.visitTypeInsn(CHECKCAST, "["+ exitTypeDescriptor + + (CodeFlow.isPrimitiveArray(exitTypeDescriptor) ? "" : ";")); yield AALOAD; } }; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Token.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Token.java index 257060262b..e7fcde56b6 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Token.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Token.java @@ -55,7 +55,7 @@ class Token { * @param startPos the exact start position * @param endPos the index of the last character */ - Token(TokenKind tokenKind, char[] tokenData, int startPos, int endPos) { + Token(TokenKind tokenKind, @Nullable char[] tokenData, int startPos, int endPos) { this.kind = tokenKind; this.data = (tokenData != null ? new String(tokenData) : null); this.startPos = startPos; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java index 24b8934ff5..947566167a 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java @@ -296,7 +296,8 @@ public abstract class ReflectionHelper { TypeDescriptor sourceType = TypeDescriptor.forObject(argument); if (argument == null) { // Perform the equivalent of GenericConversionService.convertNullSource() for a single argument. - if (targetType.getElementTypeDescriptor().getObjectType() == Optional.class) { + TypeDescriptor elementDesc = targetType.getElementTypeDescriptor(); + if (elementDesc != null && elementDesc.getObjectType() == Optional.class) { arguments[varargsPosition] = Optional.empty(); conversionOccurred = true; } @@ -383,7 +384,8 @@ public abstract class ReflectionHelper { TypeDescriptor sourceType = TypeDescriptor.forObject(argument); if (argument == null) { // Perform the equivalent of GenericConversionService.convertNullSource() for a single argument. - if (varArgContentType.getElementTypeDescriptor().getObjectType() == Optional.class) { + TypeDescriptor elementDesc = varArgContentType.getElementTypeDescriptor(); + if (elementDesc != null && elementDesc.getObjectType() == Optional.class) { arguments[varargsPosition] = Optional.empty(); conversionOccurred = true; } @@ -408,7 +410,6 @@ public abstract class ReflectionHelper { } // Otherwise, convert remaining arguments to the varargs element type. else { - Assert.state(varArgContentType != null, "No element type"); for (int i = varargsPosition; i < arguments.length; i++) { Object argument = arguments[i]; arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), varArgContentType); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProviderFactory.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProviderFactory.java index d69ee4cbc5..6935023fcf 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProviderFactory.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProviderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -88,6 +88,10 @@ public final class CallMetaDataProviderFactory { try { return JdbcUtils.extractDatabaseMetaData(dataSource, databaseMetaData -> { String databaseProductName = JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName()); + if (databaseProductName == null) { + databaseProductName = ""; + } + boolean accessProcedureColumnMetaData = context.isAccessCallParameterMetaData(); if (context.isFunction()) { if (!supportedDatabaseProductsForFunctions.contains(databaseProductName)) { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java index ee59e43ec4..82c4358c06 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java @@ -438,7 +438,7 @@ public class TableMetaDataContext { this.quoting = StringUtils.hasText(identifierQuoteString); } - public void appendTo(StringBuilder stringBuilder, String item) { + public void appendTo(StringBuilder stringBuilder, @Nullable String item) { if (this.quoting) { stringBuilder.append(this.identifierQuoteString) .append(item).append(this.identifierQuoteString); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryBean.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryBean.java index 01a779075a..a1e0502367 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryBean.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2024 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. @@ -84,8 +84,9 @@ public class EmbeddedDatabaseFactoryBean extends EmbeddedDatabaseFactory @Override public void destroy() { - if (this.databaseCleaner != null && getDataSource() != null) { - DatabasePopulatorUtils.execute(this.databaseCleaner, getDataSource()); + DatabasePopulator cleaner = this.databaseCleaner; + if (cleaner != null && getDataSource() != null) { + DatabasePopulatorUtils.execute(cleaner, getDataSource()); } shutdownDatabase(); } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/ChannelSendOperator.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/ChannelSendOperator.java index f0b3ea2961..40bd169340 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/ChannelSendOperator.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/ChannelSendOperator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -309,9 +309,10 @@ class ChannelSendOperator extends Mono implements Scannable { } private boolean emitCachedSignals() { - if (this.error != null) { + Throwable error = this.error; + if (error != null) { try { - requiredWriteSubscriber().onError(this.error); + requiredWriteSubscriber().onError(error); } finally { releaseCachedItem(); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/InvocableHandlerMethod.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/InvocableHandlerMethod.java index e1dc2f4aad..91b327c36b 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/InvocableHandlerMethod.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/InvocableHandlerMethod.java @@ -33,7 +33,6 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.lang.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.handler.HandlerMethod; import org.springframework.messaging.handler.invocation.MethodArgumentResolutionException; @@ -153,7 +152,7 @@ public class InvocableHandlerMethod extends HandlerMethod { MethodParameter returnType = getReturnType(); Class reactiveType = (isSuspendingFunction ? value.getClass() : returnType.getParameterType()); ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(reactiveType); - return (isAsyncVoidReturnType(returnType, adapter) ? + return (adapter != null && isAsyncVoidReturnType(returnType, adapter) ? Mono.from(adapter.toPublisher(value)) : Mono.justOrEmpty(value)); }); } @@ -200,8 +199,8 @@ public class InvocableHandlerMethod extends HandlerMethod { } } - private boolean isAsyncVoidReturnType(MethodParameter returnType, @Nullable ReactiveAdapter reactiveAdapter) { - if (reactiveAdapter != null && reactiveAdapter.supportsEmpty()) { + private boolean isAsyncVoidReturnType(MethodParameter returnType, ReactiveAdapter reactiveAdapter) { + if (reactiveAdapter.supportsEmpty()) { if (reactiveAdapter.isNoValue()) { return true; } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java index 5ece030b1d..d338f36444 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -631,8 +631,9 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana TransactionSynchronizationManager.unbindResource(sessionFactory); } TransactionSynchronizationManager.bindResource(sessionFactory, resourcesHolder.getSessionHolder()); - if (getDataSource() != null && resourcesHolder.getConnectionHolder() != null) { - TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder()); + ConnectionHolder connectionHolder = resourcesHolder.getConnectionHolder(); + if (connectionHolder != null && getDataSource() != null) { + TransactionSynchronizationManager.bindResource(getDataSource(), connectionHolder); } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java index 41192f88e0..168938c70b 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -535,8 +535,9 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources; TransactionSynchronizationManager.bindResource( obtainEntityManagerFactory(), resourcesHolder.getEntityManagerHolder()); - if (getDataSource() != null && resourcesHolder.getConnectionHolder() != null) { - TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder()); + ConnectionHolder connectionHolder = resourcesHolder.getConnectionHolder(); + if (connectionHolder != null && getDataSource() != null) { + TransactionSynchronizationManager.bindResource(getDataSource(), connectionHolder); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/GeneratedMapUtils.java b/spring-test/src/main/java/org/springframework/test/context/aot/GeneratedMapUtils.java index f6e3f8703d..08a0441ab7 100644 --- a/spring-test/src/main/java/org/springframework/test/context/aot/GeneratedMapUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/aot/GeneratedMapUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -42,14 +42,14 @@ final class GeneratedMapUtils { * @param methodName the name of the static method to invoke * @return an unmodifiable map retrieved from a static method */ - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({"rawtypes", "unchecked"}) static Map loadMap(String className, String methodName) { try { Class clazz = ClassUtils.forName(className, null); Method method = ReflectionUtils.findMethod(clazz, methodName); Assert.state(method != null, () -> "No %s() method found in %s".formatted(methodName, className)); Map map = (Map) ReflectionUtils.invokeMethod(method, null); - return Collections.unmodifiableMap(map); + return (map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap()); } catch (IllegalStateException ex) { throw ex; diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextGenerationContext.java b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextGenerationContext.java index 957a5b628f..65e630927a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextGenerationContext.java +++ b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextGenerationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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,7 @@ import org.springframework.aot.generate.ClassNameGenerator; import org.springframework.aot.generate.DefaultGenerationContext; import org.springframework.aot.generate.GeneratedFiles; import org.springframework.aot.hint.RuntimeHints; +import org.springframework.lang.Nullable; /** * Extension of {@link DefaultGenerationContext} with a custom implementation of @@ -30,6 +31,7 @@ import org.springframework.aot.hint.RuntimeHints; */ class TestContextGenerationContext extends DefaultGenerationContext { + @Nullable private final String featureName; @@ -41,8 +43,9 @@ class TestContextGenerationContext extends DefaultGenerationContext { * @param generatedFiles the generated files * @param runtimeHints the runtime hints */ - TestContextGenerationContext(ClassNameGenerator classNameGenerator, GeneratedFiles generatedFiles, - RuntimeHints runtimeHints) { + TestContextGenerationContext( + ClassNameGenerator classNameGenerator, GeneratedFiles generatedFiles, RuntimeHints runtimeHints) { + super(classNameGenerator, generatedFiles, runtimeHints); this.featureName = null; } diff --git a/spring-test/src/main/java/org/springframework/test/context/observation/MicrometerObservationRegistryTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/observation/MicrometerObservationRegistryTestExecutionListener.java index 954e245225..18bf72b672 100644 --- a/spring-test/src/main/java/org/springframework/test/context/observation/MicrometerObservationRegistryTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/observation/MicrometerObservationRegistryTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -25,6 +25,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; import org.springframework.core.Conventions; +import org.springframework.lang.Nullable; import org.springframework.test.context.TestContext; import org.springframework.test.context.support.AbstractTestExecutionListener; import org.springframework.util.ReflectionUtils; @@ -67,6 +68,7 @@ class MicrometerObservationRegistryTestExecutionListener extends AbstractTestExe static final String OBSERVATION_THREAD_LOCAL_ACCESSOR_CLASS_NAME = "io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor"; + @Nullable private static final String ERROR_MESSAGE; static { diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java index d37f4fc6b5..c3a43e594d 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -143,7 +143,7 @@ class TestPropertySourceAttributes { } private void addPropertiesAndLocations(List descriptors, String[] properties, - Class declaringClass, String encoding, boolean prepend) { + Class declaringClass, @Nullable String encoding, boolean prepend) { if (hasNoLocations(descriptors) && ObjectUtils.isEmpty(properties)) { String defaultPropertiesFile = detectDefaultPropertiesFile(declaringClass); diff --git a/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java b/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java index 7e63dfff68..e737273560 100644 --- a/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java +++ b/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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,9 +24,11 @@ import io.micrometer.common.KeyValues; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; +import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.observation.ClientHttpObservationDocumentation.HighCardinalityKeyNames; import org.springframework.http.client.observation.ClientHttpObservationDocumentation.LowCardinalityKeyNames; +import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; /** @@ -84,8 +86,10 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO } @Override + @Nullable public String getContextualName(ClientRequestObservationContext context) { - return "http " + context.getCarrier().getMethod().name().toLowerCase(); + ClientHttpRequest request = context.getCarrier(); + return (request != null ? "http " + request.getMethod().name().toLowerCase() : null); } @Override diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpResponse.java index e46bbcab2c..2af3933d0e 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -126,9 +126,9 @@ class ReactorNetty2ClientHttpResponse implements ClientHttpResponse { .flatMap(Collection::stream) .forEach(cookie -> result.add(cookie.name().toString(), ResponseCookie.fromClientResponse(cookie.name().toString(), cookie.value().toString()) - .domain(cookie.domain() != null ? cookie.domain().toString() : null) - .path(cookie.path() != null ? cookie.path().toString() : null) - .maxAge(cookie.maxAge() != null ? cookie.maxAge() : -1L) + .domain(toString(cookie.domain())) + .path(toString(cookie.path())) + .maxAge(toLong(cookie.maxAge())) .secure(cookie.isSecure()) .httpOnly(cookie.isHttpOnly()) .sameSite(getSameSite(cookie)) @@ -136,6 +136,15 @@ class ReactorNetty2ClientHttpResponse implements ClientHttpResponse { return CollectionUtils.unmodifiableMultiValueMap(result); } + @Nullable + private static String toString(@Nullable CharSequence value) { + return (value != null ? value.toString() : null); + } + + private static long toLong(@Nullable Long value) { + return (value != null ? value : -1); + } + @Nullable private static String getSameSite(HttpSetCookie cookie) { if (cookie instanceof DefaultHttpSetCookie defaultCookie && defaultCookie.sameSite() != null) { diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ChannelSendOperator.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ChannelSendOperator.java index abf0b6ab8f..e9aa4e13e7 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ChannelSendOperator.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ChannelSendOperator.java @@ -301,9 +301,10 @@ public class ChannelSendOperator extends Mono implements Scannable { } private boolean emitCachedSignals() { - if (this.error != null) { + Throwable error = this.error; + if (error != null) { try { - requiredWriteSubscriber().onError(this.error); + requiredWriteSubscriber().onError(error); } finally { releaseCachedItem(); diff --git a/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java index 4d5eda127a..8545894cff 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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,32 +205,32 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ } @Override - public boolean checkNotModified(@Nullable String eTag, long lastModifiedTimestamp) { + public boolean checkNotModified(@Nullable String etag, long lastModifiedTimestamp) { HttpServletResponse response = getResponse(); if (this.notModified || (response != null && HttpStatus.OK.value() != response.getStatus())) { return this.notModified; } // Evaluate conditions in order of precedence. // See https://datatracker.ietf.org/doc/html/rfc9110#section-13.2.2 - if (validateIfMatch(eTag)) { - updateResponseStateChanging(eTag, lastModifiedTimestamp); + if (validateIfMatch(etag)) { + updateResponseStateChanging(etag, lastModifiedTimestamp); return this.notModified; } // 2) If-Unmodified-Since else if (validateIfUnmodifiedSince(lastModifiedTimestamp)) { - updateResponseStateChanging(eTag, lastModifiedTimestamp); + updateResponseStateChanging(etag, lastModifiedTimestamp); return this.notModified; } // 3) If-None-Match - if (!validateIfNoneMatch(eTag)) { + if (!validateIfNoneMatch(etag)) { // 4) If-Modified-Since validateIfModifiedSince(lastModifiedTimestamp); } - updateResponseIdempotent(eTag, lastModifiedTimestamp); + updateResponseIdempotent(etag, lastModifiedTimestamp); return this.notModified; } - private boolean validateIfMatch(@Nullable String eTag) { + private boolean validateIfMatch(@Nullable String etag) { if (SAFE_METHODS.contains(getRequest().getMethod())) { return false; } @@ -238,37 +238,37 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ if (!ifMatchHeaders.hasMoreElements()) { return false; } - this.notModified = matchRequestedETags(ifMatchHeaders, eTag, false); + this.notModified = matchRequestedETags(ifMatchHeaders, etag, false); return true; } - private boolean validateIfNoneMatch(@Nullable String eTag) { + private boolean validateIfNoneMatch(@Nullable String etag) { Enumeration ifNoneMatchHeaders = getRequest().getHeaders(HttpHeaders.IF_NONE_MATCH); if (!ifNoneMatchHeaders.hasMoreElements()) { return false; } - this.notModified = !matchRequestedETags(ifNoneMatchHeaders, eTag, true); + this.notModified = !matchRequestedETags(ifNoneMatchHeaders, etag, true); return true; } - private boolean matchRequestedETags(Enumeration requestedETags, @Nullable String eTag, boolean weakCompare) { - eTag = padEtagIfNecessary(eTag); + private boolean matchRequestedETags(Enumeration requestedETags, @Nullable String etag, boolean weakCompare) { + etag = padEtagIfNecessary(etag); while (requestedETags.hasMoreElements()) { // Compare weak/strong ETags as per https://datatracker.ietf.org/doc/html/rfc9110#section-8.8.3 - Matcher eTagMatcher = ETAG_HEADER_VALUE_PATTERN.matcher(requestedETags.nextElement()); - while (eTagMatcher.find()) { + Matcher etagMatcher = ETAG_HEADER_VALUE_PATTERN.matcher(requestedETags.nextElement()); + while (etagMatcher.find()) { // only consider "lost updates" checks for unsafe HTTP methods - if ("*".equals(eTagMatcher.group()) && StringUtils.hasLength(eTag) + if ("*".equals(etagMatcher.group()) && StringUtils.hasLength(etag) && !SAFE_METHODS.contains(getRequest().getMethod())) { return false; } if (weakCompare) { - if (eTagWeakMatch(eTag, eTagMatcher.group(1))) { + if (etagWeakMatch(etag, etagMatcher.group(1))) { return false; } } else { - if (eTagStrongMatch(eTag, eTagMatcher.group(1))) { + if (etagStrongMatch(etag, etagMatcher.group(1))) { return false; } } @@ -288,14 +288,14 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ return "\"" + etag + "\""; } - private boolean eTagStrongMatch(@Nullable String first, @Nullable String second) { + private boolean etagStrongMatch(@Nullable String first, @Nullable String second) { if (!StringUtils.hasLength(first) || first.startsWith("W/")) { return false; } return first.equals(second); } - private boolean eTagWeakMatch(@Nullable String first, @Nullable String second) { + private boolean etagWeakMatch(@Nullable String first, @Nullable String second) { if (!StringUtils.hasLength(first) || !StringUtils.hasLength(second)) { return false; } @@ -308,12 +308,12 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ return first.equals(second); } - private void updateResponseStateChanging(@Nullable String eTag, long lastModifiedTimestamp) { + private void updateResponseStateChanging(@Nullable String etag, long lastModifiedTimestamp) { if (this.notModified && getResponse() != null) { getResponse().setStatus(HttpStatus.PRECONDITION_FAILED.value()); } else { - addCachingResponseHeaders(eTag, lastModifiedTimestamp); + addCachingResponseHeaders(etag, lastModifiedTimestamp); } } @@ -340,24 +340,24 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ } } - private void updateResponseIdempotent(@Nullable String eTag, long lastModifiedTimestamp) { + private void updateResponseIdempotent(@Nullable String etag, long lastModifiedTimestamp) { if (getResponse() != null) { boolean isHttpGetOrHead = SAFE_METHODS.contains(getRequest().getMethod()); if (this.notModified) { getResponse().setStatus(isHttpGetOrHead ? HttpStatus.NOT_MODIFIED.value() : HttpStatus.PRECONDITION_FAILED.value()); } - addCachingResponseHeaders(eTag, lastModifiedTimestamp); + addCachingResponseHeaders(etag, lastModifiedTimestamp); } } - private void addCachingResponseHeaders(@Nullable String eTag, long lastModifiedTimestamp) { - if (SAFE_METHODS.contains(getRequest().getMethod())) { + private void addCachingResponseHeaders(@Nullable String etag, long lastModifiedTimestamp) { + if (getResponse() != null && SAFE_METHODS.contains(getRequest().getMethod())) { if (lastModifiedTimestamp > 0 && parseDateValue(getResponse().getHeader(HttpHeaders.LAST_MODIFIED)) == -1) { getResponse().setDateHeader(HttpHeaders.LAST_MODIFIED, lastModifiedTimestamp); } - if (StringUtils.hasLength(eTag) && getResponse().getHeader(HttpHeaders.ETAG) == null) { - getResponse().setHeader(HttpHeaders.ETAG, padEtagIfNecessary(eTag)); + if (StringUtils.hasLength(etag) && getResponse().getHeader(HttpHeaders.ETAG) == null) { + getResponse().setHeader(HttpHeaders.ETAG, padEtagIfNecessary(etag)); } } } diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/CallableInterceptorChain.java b/spring-web/src/main/java/org/springframework/web/context/request/async/CallableInterceptorChain.java index d8721ca697..282af1a500 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/CallableInterceptorChain.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/CallableInterceptorChain.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -68,6 +68,7 @@ class CallableInterceptorChain { } } + @Nullable public Object applyPostProcess(NativeWebRequest request, Callable task, @Nullable Object concurrentResult) { Throwable exceptionResult = null; for (int i = this.preProcessIndex; i >= 0; i--) { @@ -86,7 +87,7 @@ class CallableInterceptorChain { } } } - return (exceptionResult != null) ? exceptionResult : concurrentResult; + return (exceptionResult != null ? exceptionResult : concurrentResult); } public Object triggerAfterTimeout(NativeWebRequest request, Callable task) { diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java index 030d974dcf..3fbd694e8b 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -96,7 +96,7 @@ public class DeferredResult { * timeout depends on the default of the underlying server. * @param timeoutValue timeout value in milliseconds */ - public DeferredResult(Long timeoutValue) { + public DeferredResult(@Nullable Long timeoutValue) { this(timeoutValue, () -> RESULT_NONE); } @@ -239,11 +239,11 @@ public class DeferredResult { * {@code false} if the result was already set or the async request expired * @see #isSetOrExpired() */ - public boolean setResult(T result) { + public boolean setResult(@Nullable T result) { return setResultInternal(result); } - private boolean setResultInternal(Object result) { + private boolean setResultInternal(@Nullable Object result) { // Immediate expiration check outside of the result lock if (isSetOrExpired()) { return false; diff --git a/spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java b/spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java index 5cc36eeeed..8782c9d263 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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,6 @@ import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; - /** * {@link org.springframework.web.server.WebFilter} that creates {@link Observation observations} * for HTTP exchanges. This collects information about the execution time and @@ -160,14 +159,16 @@ public class ServerHttpObservationFilter implements WebFilter { private void doOnTerminate(ServerRequestObservationContext context) { ServerHttpResponse response = context.getResponse(); - if (response.isCommitted()) { - this.observation.stop(); - } - else { - response.beforeCommit(() -> { + if (response != null) { + if (response.isCommitted()) { this.observation.stop(); - return Mono.empty(); - }); + } + else { + response.beforeCommit(() -> { + this.observation.stop(); + return Mono.empty(); + }); + } } } } diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java index 332852691e..b3d2ce7cd1 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java @@ -16,6 +16,7 @@ package org.springframework.web.method.annotation; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Map; import java.util.Objects; @@ -284,7 +285,10 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); Class parameterType = parameter.getParameterType(); if (KotlinDetector.isKotlinPresent() && KotlinDetector.isInlineClass(parameterType)) { - parameterType = BeanUtils.findPrimaryConstructor(parameterType).getParameterTypes()[0]; + Constructor ctor = BeanUtils.findPrimaryConstructor(parameterType); + if (ctor != null) { + parameterType = ctor.getParameterTypes()[0]; + } } try { arg = binder.convertIfNecessary(arg, parameterType, parameter); diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java b/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java index 34f4f754c0..a645ea82c0 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -414,14 +414,16 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa private void doOnTerminate(ServerRequestObservationContext context) { ServerHttpResponse response = context.getResponse(); - if (response.isCommitted()) { - this.observation.stop(); - } - else { - response.beforeCommit(() -> { + if (response != null) { + if (response.isCommitted()) { this.observation.stop(); - return Mono.empty(); - }); + } + else { + response.beforeCommit(() -> { + this.observation.stop(); + return Mono.empty(); + }); + } } } } diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/RegexPathElement.java b/spring-web/src/main/java/org/springframework/web/util/pattern/RegexPathElement.java index 4023d2def5..dff0609ac4 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/RegexPathElement.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/RegexPathElement.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -134,7 +134,7 @@ class RegexPathElement extends PathElement { if (matches) { if (isNoMorePattern()) { if (matchingContext.determineRemainingPath && - (this.variableNames.isEmpty() || textToMatch.length() > 0)) { + (this.variableNames.isEmpty() || !textToMatch.isEmpty())) { matchingContext.remainingPathIndex = pathIndex + 1; matches = true; } @@ -142,9 +142,9 @@ class RegexPathElement extends PathElement { // No more pattern, is there more data? // If pattern is capturing variables there must be some actual data to bind to them matches = (pathIndex + 1 >= matchingContext.pathLength) && - (this.variableNames.isEmpty() || textToMatch.length() > 0); + (this.variableNames.isEmpty() || !textToMatch.isEmpty()); if (!matches && matchingContext.isMatchOptionalTrailingSeparator()) { - matches = (this.variableNames.isEmpty() || textToMatch.length() > 0) && + matches = (this.variableNames.isEmpty() || !textToMatch.isEmpty()) && (pathIndex + 2 >= matchingContext.pathLength) && matchingContext.isSeparator(pathIndex + 1); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java index b2b0fda53e..55f9602bef 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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 io.micrometer.common.KeyValues; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; +import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; import org.springframework.web.reactive.function.client.ClientHttpObservationDocumentation.HighCardinalityKeyNames; import org.springframework.web.reactive.function.client.ClientHttpObservationDocumentation.LowCardinalityKeyNames; @@ -88,8 +89,10 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO } @Override + @Nullable public String getContextualName(ClientRequestObservationContext context) { - return "http " + context.getRequest().method().name().toLowerCase(); + ClientRequest request = context.getRequest(); + return (request != null ? "http " + request.method().name().toLowerCase() : null); } @Override diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java index ae2a9909dc..c03b6f7808 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -94,7 +94,7 @@ public class ResourceUrlProvider implements ApplicationListener 1) { Collections.sort(this.expressions); } } - private static List parseExpressions(String[] consumes, String[] headers) { + private static List parseExpressions(@Nullable String[] consumes, @Nullable String[] headers) { Set result = null; if (!ObjectUtils.isEmpty(headers)) { for (String header : headers) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/ProducesRequestCondition.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/ProducesRequestCondition.java index ea4ae815e0..3d51b46b6c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/ProducesRequestCondition.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/ProducesRequestCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -81,7 +81,7 @@ public final class ProducesRequestCondition extends AbstractRequestCondition 1) { Collections.sort(this.expressions); } - this.contentTypeResolver = resolver != null ? resolver : DEFAULT_CONTENT_TYPE_RESOLVER; + this.contentTypeResolver = (resolver != null ? resolver : DEFAULT_CONTENT_TYPE_RESOLVER); } - private List parseExpressions(String[] produces, String[] headers) { + private List parseExpressions(@Nullable String[] produces, @Nullable String[] headers) { Set result = null; if (!ObjectUtils.isEmpty(headers)) { for (String header : headers) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueArgumentResolver.java index 4aff5cfd2c..fb98c07905 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueArgumentResolver.java @@ -16,6 +16,7 @@ package org.springframework.web.reactive.result.method.annotation; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Map; import java.util.Objects; @@ -200,7 +201,10 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr WebDataBinder binder = bindingContext.createDataBinder(exchange, namedValueInfo.name); Class parameterType = parameter.getParameterType(); if (KotlinDetector.isKotlinPresent() && KotlinDetector.isInlineClass(parameterType)) { - parameterType = BeanUtils.findPrimaryConstructor(parameterType).getParameterTypes()[0]; + Constructor ctor = BeanUtils.findPrimaryConstructor(parameterType); + if (ctor != null) { + parameterType = ctor.getParameterTypes()[0]; + } } try { value = binder.convertIfNecessary(value, parameterType, parameter); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelInitializer.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelInitializer.java index 033df46de4..a062050a7f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelInitializer.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -32,7 +32,6 @@ import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.ModelAttribute; @@ -74,7 +73,6 @@ class ModelInitializer { * @param exchange the current exchange * @return a {@code Mono} for when the model is populated. */ - @SuppressWarnings("Convert2MethodRef") public Mono initModel(HandlerMethod handlerMethod, InitBinderBindingContext bindingContext, ServerWebExchange exchange) { @@ -124,7 +122,7 @@ class ModelInitializer { ResolvableType type = handlerResult.getReturnType(); MethodParameter typeSource = handlerResult.getReturnTypeSource(); ReactiveAdapter adapter = this.adapterRegistry.getAdapter(type.resolve(), value); - if (isAsyncVoidType(type, typeSource, adapter)) { + if (adapter != null && isAsyncVoidType(type, typeSource, adapter)) { return Mono.from(adapter.toPublisher(value)); } String name = getAttributeName(typeSource); @@ -134,10 +132,10 @@ class ModelInitializer { } - private boolean isAsyncVoidType(ResolvableType type, MethodParameter typeSource, @Nullable ReactiveAdapter adapter) { + private boolean isAsyncVoidType(ResolvableType type, MethodParameter typeSource, ReactiveAdapter adapter) { Method method = typeSource.getMethod(); - return (adapter != null && (adapter.isNoValue() || type.resolveGeneric() == Void.class)) || - (method != null && KotlinDetector.isSuspendingFunction(method) && typeSource.getParameterType() == void.class); + return (adapter.isNoValue() || type.resolveGeneric() == Void.class || (method != null && + KotlinDetector.isSuspendingFunction(method) && typeSource.getParameterType() == void.class)); } private String getAttributeName(MethodParameter param) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java index ea158774a0..36ce770c29 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -94,7 +94,6 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand isSupportedType(result.getReturnType().getGeneric().toClass()); } - @Nullable private static Class resolveReturnValueType(HandlerResult result) { Class valueType = result.getReturnType().toClass(); Object value = result.getReturnValue(); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java index 999d0cb290..d4a455b623 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java @@ -76,14 +76,14 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition 1) { Collections.sort(this.expressions); } } - private static List parseExpressions(String[] consumes, @Nullable String[] headers) { + private static List parseExpressions(@Nullable String[] consumes, @Nullable String[] headers) { Set result = null; if (!ObjectUtils.isEmpty(headers)) { for (String header : headers) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java index adb876d27b..fbb5dc1ebf 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java @@ -83,7 +83,7 @@ public final class ProducesRequestCondition extends AbstractRequestCondition 1) { Collections.sort(this.expressions); } - this.contentNegotiationManager = manager != null ? manager : DEFAULT_CONTENT_NEGOTIATION_MANAGER; + this.contentNegotiationManager = (manager != null ? manager : DEFAULT_CONTENT_NEGOTIATION_MANAGER); } - private List parseExpressions(String[] produces, @Nullable String[] headers) { + private List parseExpressions(@Nullable String[] produces, @Nullable String[] headers) { Set result = null; if (!ObjectUtils.isEmpty(headers)) { for (String header : headers) {