From 6da9aed0555b059b052bb249643044ae7d72721b Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 8 Sep 2023 15:21:40 +0200 Subject: [PATCH 1/4] Update copyright header --- .../method/annotation/RequestResponseBodyMethodProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java index 65f62dd7175..fe1b5ffdc8d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2023 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. From 40f1cf67bd043ca83ffaea8a4581c04e1cbfac91 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 7 Sep 2023 17:09:13 +0200 Subject: [PATCH 2/4] Polish DefaultClientResponseTests and suppress "unchecked" warnings --- .../client/DefaultClientResponseTests.java | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java index 0ad854924a1..58c1d773560 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java @@ -18,7 +18,6 @@ package org.springframework.web.reactive.function.client; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.OptionalLong; @@ -94,8 +93,7 @@ class DefaultClientResponseTests { httpHeaders.setContentType(contentType); InetSocketAddress host = InetSocketAddress.createUnresolved("localhost", 80); httpHeaders.setHost(host); - List range = Collections.singletonList(HttpRange.createByteRange(0, 42)); - httpHeaders.setRange(range); + httpHeaders.setRange(List.of(HttpRange.createByteRange(0, 42))); given(mockResponse.getHeaders()).willReturn(httpHeaders); @@ -124,7 +122,7 @@ class DefaultClientResponseTests { mockTextPlainResponse(Flux.just(dataBuffer)); given(mockExchangeStrategies.messageReaders()).willReturn( - Collections.singletonList(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); + List.of(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); Mono resultMono = defaultClientResponse.body(toMono(String.class)); assertThat(resultMono.block()).isEqualTo("foo"); @@ -137,7 +135,7 @@ class DefaultClientResponseTests { mockTextPlainResponse(Flux.just(dataBuffer)); given(mockExchangeStrategies.messageReaders()).willReturn( - Collections.singletonList(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); + List.of(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); Mono resultMono = defaultClientResponse.bodyToMono(String.class); assertThat(resultMono.block()).isEqualTo("foo"); @@ -150,7 +148,7 @@ class DefaultClientResponseTests { mockTextPlainResponse(Flux.just(dataBuffer)); given(mockExchangeStrategies.messageReaders()).willReturn( - Collections.singletonList(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); + List.of(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); Mono resultMono = defaultClientResponse.bodyToMono(STRING_TYPE); assertThat(resultMono.block()).isEqualTo("foo"); @@ -163,11 +161,11 @@ class DefaultClientResponseTests { mockTextPlainResponse(Flux.just(dataBuffer)); given(mockExchangeStrategies.messageReaders()).willReturn( - Collections.singletonList(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); + List.of(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); Flux resultFlux = defaultClientResponse.bodyToFlux(String.class); Mono> result = resultFlux.collectList(); - assertThat(result.block()).isEqualTo(Collections.singletonList("foo")); + assertThat(result.block()).containsExactly("foo"); } @Test @@ -177,11 +175,11 @@ class DefaultClientResponseTests { mockTextPlainResponse(Flux.just(dataBuffer)); given(mockExchangeStrategies.messageReaders()).willReturn( - Collections.singletonList(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); + List.of(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); Flux resultFlux = defaultClientResponse.bodyToFlux(STRING_TYPE); Mono> result = resultFlux.collectList(); - assertThat(result.block()).isEqualTo(Collections.singletonList("foo")); + assertThat(result.block()).containsExactly("foo"); } @Test @@ -192,7 +190,7 @@ class DefaultClientResponseTests { mockTextPlainResponse(Flux.just(dataBuffer)); given(mockExchangeStrategies.messageReaders()).willReturn( - Collections.singletonList(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); + List.of(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); ResponseEntity result = defaultClientResponse.toEntity(String.class).block(); assertThat(result.getBody()).isEqualTo("foo"); @@ -213,7 +211,7 @@ class DefaultClientResponseTests { given(mockResponse.getBody()).willReturn(Flux.just(dataBuffer)); given(mockExchangeStrategies.messageReaders()).willReturn( - Collections.singletonList(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); + List.of(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); ResponseEntity result = defaultClientResponse.toEntity(String.class).block(); assertThat(result.getBody()).isEqualTo("foo"); @@ -230,7 +228,7 @@ class DefaultClientResponseTests { mockTextPlainResponse(Flux.just(dataBuffer)); given(mockExchangeStrategies.messageReaders()).willReturn( - Collections.singletonList(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); + List.of(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); ResponseEntity result = defaultClientResponse.toEntity(STRING_TYPE).block(); assertThat(result.getBody()).isEqualTo("foo"); @@ -247,10 +245,10 @@ class DefaultClientResponseTests { mockTextPlainResponse(Flux.just(dataBuffer)); given(mockExchangeStrategies.messageReaders()).willReturn( - Collections.singletonList(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); + List.of(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); ResponseEntity> result = defaultClientResponse.toEntityList(String.class).block(); - assertThat(result.getBody()).isEqualTo(Collections.singletonList("foo")); + assertThat(result.getBody()).containsExactly("foo"); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(result.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value()); assertThat(result.getHeaders().getContentType()).isEqualTo(MediaType.TEXT_PLAIN); @@ -268,10 +266,10 @@ class DefaultClientResponseTests { given(mockResponse.getBody()).willReturn(Flux.just(dataBuffer)); given(mockExchangeStrategies.messageReaders()).willReturn( - Collections.singletonList(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); + List.of(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); ResponseEntity> result = defaultClientResponse.toEntityList(String.class).block(); - assertThat(result.getBody()).isEqualTo(Collections.singletonList("foo")); + assertThat(result.getBody()).containsExactly("foo"); assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(999)); assertThat(result.getStatusCodeValue()).isEqualTo(999); assertThat(result.getHeaders().getContentType()).isEqualTo(MediaType.TEXT_PLAIN); @@ -286,10 +284,10 @@ class DefaultClientResponseTests { mockTextPlainResponse(Flux.just(dataBuffer)); given(mockExchangeStrategies.messageReaders()).willReturn( - Collections.singletonList(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); + List.of(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()))); ResponseEntity> result = defaultClientResponse.toEntityList(STRING_TYPE).block(); - assertThat(result.getBody()).isEqualTo(Collections.singletonList("foo")); + assertThat(result.getBody()).containsExactly("foo"); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(result.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value()); assertThat(result.getHeaders().getContentType()).isEqualTo(MediaType.TEXT_PLAIN); @@ -305,18 +303,18 @@ class DefaultClientResponseTests { given(mockResponse.getBody()).willReturn(Flux.just(dataBuffer)); given(mockExchangeStrategies.messageReaders()).willReturn( - Collections.singletonList(new DecoderHttpMessageReader<>(new ByteArrayDecoder()))); + List.of(new DecoderHttpMessageReader<>(new ByteArrayDecoder()))); Mono resultMono = defaultClientResponse.createException(); WebClientResponseException exception = resultMono.block(); assertThat(exception.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(exception.getMessage()).isEqualTo("404 Not Found"); - assertThat(exception.getHeaders()).containsExactly(entry("Content-Type", - Collections.singletonList("text/plain"))); + assertThat(exception.getHeaders()).containsExactly(entry("Content-Type", List.of("text/plain"))); assertThat(exception.getResponseBodyAsByteArray()).isEqualTo(bytes); } @Test + @SuppressWarnings("unchecked") void createExceptionAndDecodeContent() { byte[] bytes = "{\"name\":\"Jason\"}".getBytes(StandardCharsets.UTF_8); DataBuffer buffer = DefaultDataBufferFactory.sharedInstance.wrap(bytes); @@ -330,10 +328,11 @@ class DefaultClientResponseTests { new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()))); WebClientResponseException ex = defaultClientResponse.createException().block(); - assertThat(ex.getResponseBodyAs(Map.class)).hasSize(1).containsEntry("name", "Jason"); + assertThat(ex.getResponseBodyAs(Map.class)).containsExactly(entry("name", "Jason")); } @Test + @SuppressWarnings("unchecked") void createExceptionAndDecodeWithoutContent() { byte[] bytes = new byte[0]; DataBuffer buffer = DefaultDataBufferFactory.sharedInstance.wrap(bytes); @@ -360,7 +359,7 @@ class DefaultClientResponseTests { given(mockResponse.getBody()).willReturn(Flux.just(dataBuffer)); given(mockExchangeStrategies.messageReaders()).willReturn( - Collections.singletonList(new DecoderHttpMessageReader<>(new ByteArrayDecoder()))); + List.of(new DecoderHttpMessageReader<>(new ByteArrayDecoder()))); Mono resultMono = defaultClientResponse.createError(); StepVerifier.create(resultMono) @@ -369,10 +368,8 @@ class DefaultClientResponseTests { WebClientResponseException exception = (WebClientResponseException) t; assertThat(exception.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(exception.getMessage()).isEqualTo("404 Not Found"); - assertThat(exception.getHeaders()).containsExactly(entry("Content-Type", - Collections.singletonList("text/plain"))); + assertThat(exception.getHeaders()).containsExactly(entry("Content-Type",List.of("text/plain"))); assertThat(exception.getResponseBodyAsByteArray()).isEqualTo(bytes); - }) .verify(); } From 311c58ea2daaa6ba4965e6a36b86e1af4698dae3 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 8 Sep 2023 15:02:25 +0200 Subject: [PATCH 3/4] Polish [Standard]TypeLocator --- .../expression/TypeLocator.java | 12 +++-- .../spel/support/StandardTypeLocator.java | 54 ++++++++++--------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/TypeLocator.java b/spring-expression/src/main/java/org/springframework/expression/TypeLocator.java index ec1c531bb52..91ef19ed0c8 100644 --- a/spring-expression/src/main/java/org/springframework/expression/TypeLocator.java +++ b/spring-expression/src/main/java/org/springframework/expression/TypeLocator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2023 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. @@ -18,8 +18,9 @@ package org.springframework.expression; /** * Implementers of this interface are expected to be able to locate types. - * They may use a custom {@link ClassLoader} and/or deal with common - * package prefixes (e.g. {@code java.lang}) however they wish. + * + *

They may use a custom {@link ClassLoader} and/or deal with common package + * prefixes (for example, {@code java.lang}) however they wish. * *

See {@link org.springframework.expression.spel.support.StandardTypeLocator} * for an example implementation. @@ -31,8 +32,9 @@ package org.springframework.expression; public interface TypeLocator { /** - * Find a type by name. The name may or may not be fully qualified - * (e.g. {@code String} or {@code java.lang.String}). + * Find a type by name. + *

The name may or may not be fully qualified — for example, + * {@code String} or {@code java.lang.String}. * @param typeName the type to be located * @return the {@code Class} object representing that type * @throws EvaluationException if there is a problem finding the type diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeLocator.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeLocator.java index dafb615271e..aa6ada780b0 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeLocator.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeLocator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2023 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. @@ -28,12 +28,16 @@ import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; /** - * A simple implementation of {@link TypeLocator} that uses the context ClassLoader - * (or any ClassLoader set upon it). It supports 'well-known' packages: So if a - * type cannot be found, it will try the registered imports to locate it. + * A simple implementation of {@link TypeLocator} that uses the default + * {@link ClassLoader} or a supplied {@link ClassLoader} to locate types. + * + *

Supports well-known packages, registered as + * {@linkplain #registerImport(String) import prefixes}. If a type cannot be found, + * this class will attempt to locate it using the registered import prefixes. * * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public class StandardTypeLocator implements TypeLocator { @@ -41,20 +45,21 @@ public class StandardTypeLocator implements TypeLocator { @Nullable private final ClassLoader classLoader; - private final List knownPackagePrefixes = new ArrayList<>(1); + private final List importPrefixes = new ArrayList<>(1); /** - * Create a StandardTypeLocator for the default ClassLoader - * (typically, the thread context ClassLoader). + * Create a {@code StandardTypeLocator} for the default {@link ClassLoader} + * (typically, the thread context {@code ClassLoader}). + * @see ClassUtils#getDefaultClassLoader() */ public StandardTypeLocator() { this(ClassUtils.getDefaultClassLoader()); } /** - * Create a StandardTypeLocator for the given ClassLoader. - * @param classLoader the ClassLoader to delegate to + * Create a {@code StandardTypeLocator} for the given {@link ClassLoader}. + * @param classLoader the {@code ClassLoader} to delegate to */ public StandardTypeLocator(@Nullable ClassLoader classLoader) { this.classLoader = classLoader; @@ -65,49 +70,48 @@ public class StandardTypeLocator implements TypeLocator { /** * Register a new import prefix that will be used when searching for unqualified types. - * Expected format is something like "java.lang". - * @param prefix the prefix to register + *

Expected format is something like {@code "java.lang"}. + * @param prefix the import prefix to register */ public void registerImport(String prefix) { - this.knownPackagePrefixes.add(prefix); + this.importPrefixes.add(prefix); } /** - * Remove that specified prefix from this locator's list of imports. - * @param prefix the prefix to remove + * Remove the specified prefix from this locator's list of imports. + * @param prefix the import prefix to remove */ public void removeImport(String prefix) { - this.knownPackagePrefixes.remove(prefix); + this.importPrefixes.remove(prefix); } /** - * Return a list of all the import prefixes registered with this StandardTypeLocator. - * @return a list of registered import prefixes + * Get the list of import prefixes registered with this {@code StandardTypeLocator}. + * @return the list of registered import prefixes */ public List getImportPrefixes() { - return Collections.unmodifiableList(this.knownPackagePrefixes); + return Collections.unmodifiableList(this.importPrefixes); } /** - * Find a (possibly unqualified) type reference - first using the type name as-is, - * then trying any registered prefixes if the type name cannot be found. + * Find a (possibly unqualified) type reference, first using the type name as-is, + * and then trying any registered import prefixes if the type name cannot be found. * @param typeName the type to locate * @return the class object for the type * @throws EvaluationException if the type cannot be found */ @Override public Class findType(String typeName) throws EvaluationException { - String nameToLookup = typeName; try { - return ClassUtils.forName(nameToLookup, this.classLoader); + return ClassUtils.forName(typeName, this.classLoader); } - catch (ClassNotFoundException ey) { + catch (ClassNotFoundException ex) { // try any registered prefixes before giving up } - for (String prefix : this.knownPackagePrefixes) { + for (String prefix : this.importPrefixes) { try { - nameToLookup = prefix + '.' + typeName; + String nameToLookup = prefix + '.' + typeName; return ClassUtils.forName(nameToLookup, this.classLoader); } catch (ClassNotFoundException ex) { From ea4105165119937d1da59704be053f4023d56a9c Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 8 Sep 2023 16:03:00 +0200 Subject: [PATCH 4/4] Do not invoke [Map|Collection].isEmpty() in nullSafeConciseToString() gh-30810 introduced explicit support for collections and maps in ObjectUtils.nullSafeConciseToString() by invoking isEmpty() on a Map or Collection to determine which concise string representation should be used. However, this caused a regression in which an exception was thrown if the Map or Collection was a proxy generated by AbstractFactoryBean to support , , and in XML configuration. This commit addresses this set of regressions by always returning "[...]" or "{...}" for a Collection or Map, respectively, disregarding whether the map is empty or not. Closes gh-31138 --- .../org/springframework/util/ObjectUtils.java | 18 ++++++++---------- .../springframework/util/ObjectUtilsTests.java | 8 ++++---- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/util/ObjectUtils.java b/spring-core/src/main/java/org/springframework/util/ObjectUtils.java index 38621f2f5d7..10822e3c257 100644 --- a/spring-core/src/main/java/org/springframework/util/ObjectUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ObjectUtils.java @@ -70,7 +70,6 @@ public abstract class ObjectUtils { private static final String ARRAY_ELEMENT_SEPARATOR = ", "; private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; private static final String NON_EMPTY_ARRAY = ARRAY_START + "..." + ARRAY_END; - private static final String EMPTY_COLLECTION = "[]"; private static final String NON_EMPTY_COLLECTION = "[...]"; @@ -915,10 +914,9 @@ public abstract class ObjectUtils { *

  • {@code"Optional[]"} if {@code obj} is a non-empty {@code Optional}, * where {@code } is the result of invoking {@link #nullSafeConciseToString} * on the object contained in the {@code Optional}
  • - *
  • {@code "{}"} if {@code obj} is an empty array or {@link Map}
  • - *
  • {@code "{...}"} if {@code obj} is a non-empty array or {@link Map}
  • - *
  • {@code "[]"} if {@code obj} is an empty {@link Collection}
  • - *
  • {@code "[...]"} if {@code obj} is a non-empty {@link Collection}
  • + *
  • {@code "{}"} if {@code obj} is an empty array
  • + *
  • {@code "{...}"} if {@code obj} is a {@link Map} or a non-empty array
  • + *
  • {@code "[...]"} if {@code obj} is a {@link Collection}
  • *
  • {@linkplain Class#getName() Class name} if {@code obj} is a {@link Class}
  • *
  • {@linkplain Charset#name() Charset name} if {@code obj} is a {@link Charset}
  • *
  • {@linkplain TimeZone#getID() TimeZone ID} if {@code obj} is a {@link TimeZone}
  • @@ -953,12 +951,12 @@ public abstract class ObjectUtils { if (obj.getClass().isArray()) { return (Array.getLength(obj) == 0 ? EMPTY_ARRAY : NON_EMPTY_ARRAY); } - if (obj instanceof Collection collection) { - return (collection.isEmpty() ? EMPTY_COLLECTION : NON_EMPTY_COLLECTION); + if (obj instanceof Collection) { + return NON_EMPTY_COLLECTION; } - if (obj instanceof Map map) { - // EMPTY_ARRAY and NON_EMPTY_ARRAY are also used for maps. - return (map.isEmpty() ? EMPTY_ARRAY : NON_EMPTY_ARRAY); + if (obj instanceof Map) { + // NON_EMPTY_ARRAY is also used for maps. + return NON_EMPTY_ARRAY; } if (obj instanceof Class clazz) { return clazz.getName(); diff --git a/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java index e67f964c3b9..8090c4be82f 100644 --- a/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java @@ -1052,8 +1052,8 @@ class ObjectUtilsTests { void nullSafeConciseToStringForEmptyCollections() { List list = List.of(); Set set = Set.of(); - assertThat(ObjectUtils.nullSafeConciseToString(list)).isEqualTo("[]"); - assertThat(ObjectUtils.nullSafeConciseToString(set)).isEqualTo("[]"); + assertThat(ObjectUtils.nullSafeConciseToString(list)).isEqualTo("[...]"); + assertThat(ObjectUtils.nullSafeConciseToString(set)).isEqualTo("[...]"); } @Test @@ -1066,8 +1066,8 @@ class ObjectUtilsTests { @Test void nullSafeConciseToStringForEmptyMaps() { - Map map = new HashMap(); - assertThat(ObjectUtils.nullSafeConciseToString(map)).isEqualTo("{}"); + Map map = Map.of(); + assertThat(ObjectUtils.nullSafeConciseToString(map)).isEqualTo("{...}"); } @Test