Compare commits

...

140 Commits

Author SHA1 Message Date
Spring Builds e6585e0250 Release v6.1.0 2023-11-16 14:21:14 +00:00
rstoyanchev af1b3c72d5 Merge branch '6.0.x' 2023-11-16 11:15:44 +00:00
rstoyanchev 770cbd2fb5 Revise exception handling in HandlerMappingIntrospector
See gh-31588
2023-11-16 11:15:29 +00:00
Juergen Hoeller d1867f1b73 Merge branch '6.0.x' 2023-11-16 11:35:12 +01:00
Juergen Hoeller e5f04e5ddf Polishing 2023-11-16 11:34:31 +01:00
Juergen Hoeller 99378fe947 Polishing 2023-11-16 11:22:09 +01:00
Juergen Hoeller b0e29acd23 Upgrade to SmallRye Mutiny 1.10.0 2023-11-16 11:22:01 +01:00
Brian Clozel c02f735056 Merge branch '6.0.x' 2023-11-16 09:04:09 +01:00
Brian Clozel c18784678d Reduce allocations in server conventions
This commit optimizes the default observation conventions to reduce
`KeyValues` allocations.
2023-11-16 09:00:24 +01:00
Stéphane Nicoll c485e9b74f Merge branch '6.0.x' 2023-11-15 20:50:02 +01:00
Stéphane Nicoll 51cdff591c Merge pull request #31612 from PiotrFLEURY
* pr/31612:
  Polish "Provide invalid class name in exception message"
  Provide invalid class name in exception message

Closes gh-31612
2023-11-15 20:49:55 +01:00
Stéphane Nicoll d93114df9a Polish "Provide invalid class name in exception message"
See gh-31612
2023-11-15 20:45:24 +01:00
PiotrFLEURY 5ac4c3bd76 Provide invalid class name in exception message
See gh-31612
2023-11-15 20:40:45 +01:00
rstoyanchev 3a70c71c5e Merge branch '6.0.x' 2023-11-15 18:57:35 +00:00
rstoyanchev 05c3ffb2fb Use InvalidMimeTypeException in MimeTypeUtils#sortBySpecificity
Closes gh-31254
2023-11-15 18:57:23 +00:00
rstoyanchev 19e8ed130c Cache Filter for HandlerMappingIntrospector and log warnings
See gh-31588
2023-11-15 18:57:23 +00:00
Stéphane Nicoll f15b8b95ad Merge branch '6.0.x' 2023-11-15 19:04:11 +01:00
Sébastien Deleuze 183c2f82b2 Refine @EmbeddableInstantiator reflection hints
Based on @odrotbohm proposal to manage a wider set
of use cases.

Closes gh-31534
2023-11-15 18:48:27 +01:00
Stéphane Nicoll 4464251754 Add missing runtime hints for ProblemDetail mixins
Closes gh-31606
2023-11-15 18:44:17 +01:00
Brian Clozel 8eebb5e27b Polish
See gh-31575
2023-11-15 15:26:49 +01:00
Arjen Poutsma 0ef3cb9b9e Merge branch '6.0.x' 2023-11-15 14:33:55 +01:00
Arjen Poutsma 8868fe2ea5 Fix position bug in NettyDataBuffer::toByteBuffer
Closes gh-31605
2023-11-15 14:23:43 +01:00
Juergen Hoeller ca9634143c Upgrade to Reactor 2023.0.0
Includes Protobuf 3.25.0, Artemis 2.31.2, Checkstyle 10.12.5

Closes gh-31557
2023-11-15 14:06:43 +01:00
Juergen Hoeller 0e6bf098cb Merge branch '6.0.x'
# Conflicts:
#	build.gradle
#	framework-platform/framework-platform.gradle
#	spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java
2023-11-15 13:36:03 +01:00
Juergen Hoeller c373f496f3 Consistent ordering of overloaded operations 2023-11-15 13:29:35 +01:00
Juergen Hoeller eb1883bdc4 Upgrade to Reactor 2022.0.13 and Netty 4.1.101
Includes RxJava 3.1.8, Apache HttpComponents Core Reactive 5.2.3, POI 5.2.4, Commons IO 2.15, WebJars Locator 0.55, OpenPDF 1.3.33, JRuby 9.4.5, H2 2.2.224, ActiveMQ 5.17.6, Checkstyle 10.12.5

Closes gh-31585
2023-11-15 13:29:13 +01:00
Juergen Hoeller 01fc1ee3bb Introduce update variant with KeyHolder and explicit key column names
Closes gh-31607
2023-11-15 12:20:22 +01:00
Stéphane Nicoll 510caad6c3 Merge branch '6.0.x' 2023-11-15 12:07:50 +01:00
Stéphane Nicoll df6f66110f Fix wrong nullability requirement
Closes gh-31610
2023-11-15 12:01:26 +01:00
Sébastien Deleuze 92c3843dae Add required AspectJ field hints
This commit adds reflection hints on fields for
classes compiled by AspectJ.

Closes gh-31575
2023-11-15 10:30:02 +01:00
rstoyanchev e12269ef1a Merge branch '6.0.x' 2023-11-14 20:01:14 +00:00
rstoyanchev ac235a0c43 Fix checkstyle violation 2023-11-14 20:00:16 +00:00
rstoyanchev 2613dfce0b Merge branch '6.0.x' 2023-11-14 19:24:43 +00:00
rstoyanchev a4e3af5cbe Revise HandlerMappingIntrospector caching
Expose methods to set and reset cache to use from a Filter instead
of a method to create such a Filter. Also use cached results only
if they match by dispatcher type and requestURI.

See gh-31588
2023-11-14 19:24:29 +00:00
Sam Brannen 978e81e72e Merge branch '6.0.x' 2023-11-14 15:15:53 +01:00
Sam Brannen e71117dcdf Polish contribution
See gh-31598
2023-11-14 14:46:34 +01:00
Jason d5874ab99e Avoid duplicate resources in PathMatchingResourcePatternResolver on Windows
This commit updates PathMatchingResourcePatternResolver to avoid
returning duplicate resources on MS Windows when searching using the
`classpath*:` prefix and a wildcard pattern that matches resources
which are directly present in a JAR as well as present via classpath
manifest entries.

Closes gh-31598
2023-11-14 14:39:48 +01:00
Juergen Hoeller 2b7a9209b3 Merge branch '6.0.x' 2023-11-14 12:52:25 +01:00
Juergen Hoeller 99327b7db1 Preserve nested square brackets within parameter name
Closes gh-31596
2023-11-14 12:51:19 +01:00
Juergen Hoeller 3e06441d97 Cache SpEL-loaded types in StandardTypeLocator
Closes gh-31579
2023-11-14 12:46:16 +01:00
Arjen Poutsma 8f21479234 Add body conversion capabilities in RestClient::exchange
This commit introduces a ConvertibleClientHttpResponse type that
extends ClientHttpResponse, and that can convert the body to a desired
type. Before this commit, it was not easy to use the configured HTTP
message converters in combination with RestClient::exchange.

Closes gh-31597
2023-11-14 11:18:59 +01:00
Stéphane Nicoll dd97dee7fd Polish 2023-11-13 19:33:09 +01:00
Sébastien Deleuze eb3982b6c2 Property-driven onRefresh exit for AppCDS purpose
This commit allows to terminate the JVM when the
-Dspring.context.exit=onRefresh property is set,
which can be useful for AppCDS training run in order
to get most of the AppCDS cache without starting the
beans.

Closes gh-31595
2023-11-13 15:53:57 +01:00
Brian Clozel 258f99a8bc Merge branch '6.0.x' 2023-11-13 14:29:16 +01:00
Brian Clozel 01f2925048 Upgrade to Micrometer 1.10.13
Closes gh-31586
2023-11-13 14:28:45 +01:00
Brian Clozel dced3539a3 Upgrade to Context Propagation 1.1.0
Closes gh-31599
2023-11-13 14:15:39 +01:00
Brian Clozel bb66885426 Upgrade to Micrometer 1.12.0
Closes gh-31556
2023-11-13 14:15:02 +01:00
Stéphane Nicoll 7f615fd8af Fix reference in deprecation message of parseForwardedFor 2023-11-11 18:05:36 +01:00
rstoyanchev 33b1ff5d27 Merge branch '6.0.x' 2023-11-10 17:45:24 +00:00
rstoyanchev 44a37000ec HandlerMappingIntrospector exposes Filter for caching
Closes gh-31588
2023-11-10 17:43:52 +00:00
rstoyanchev 53fe5fafed Minor refactoring in HandlerMappingIntrospector
See gh-31588
2023-11-10 17:27:54 +00:00
Stéphane Nicoll f5453cc445 Polish 2023-11-10 16:42:43 +01:00
rstoyanchev b9bd98fc5b Polishing in HandlerMappingIntrospector
See gh-31588
2023-11-10 11:27:44 +00:00
Simon Baslé a97ecf7a29 Link to KDoc/Javadoc in their respective counterpart overview pages
This commit adds a link in the package-overview pages of KDoc and
Javadoc, pointing to the other documentation's own page.

In order for local tests to work, the build has been slightly modified
to align with the directory structure in the distributed documentation
zip (and consequently with that of the deployed documentation website).

Lastly, the javadoc build was fixed to include the `overview.html` again
in the resulting HTML index, since this is where the KDoc link is added.

See gh-28055
Closes gh-31587
2023-11-10 11:36:27 +01:00
Sam Brannen 8a7144ba42 Document how to log @⁠Sql scripts and statements
Closes gh-31589
2023-11-10 11:19:38 +01:00
Sam Brannen 6c3a3dc7d9 Polishing 2023-11-10 11:18:26 +01:00
Sam Brannen b1a7161218 Document @⁠Sql class-level execution phase support in the reference manual
Closes gh-31377
2023-11-09 15:22:14 +01:00
Sam Brannen 332de60586 Update @⁠Sql docs regarding Kotlin and Java 8 2023-11-09 15:19:52 +01:00
rstoyanchev 769128a0b2 Merge branch '6.0.x' 2023-11-09 12:53:36 +00:00
rstoyanchev 7714110940 Polishing in DefaultWebClient 2023-11-09 12:53:15 +00:00
rstoyanchev 75d1278bde Include port when logging URI in DefaultWebClient
Closes gh-30519
2023-11-09 12:53:15 +00:00
rstoyanchev d8441fc80c Invoke handleEmptyBody if there is no content-type
Closes gh-30522
2023-11-09 12:53:05 +00:00
Sébastien Deleuze d8ed7c7906 Merge branch '6.0.x' 2023-11-09 13:11:58 +01:00
Sébastien Deleuze 620f558547 Register hints for superclass in BindingReflectionHintsRegistrar
Closes gh-31552
2023-11-09 13:11:36 +01:00
Juergen Hoeller 8841b7c3bb Upgrade to SnakeYAML 2.2 2023-11-09 11:52:02 +01:00
Juergen Hoeller 6c00e6162b Merge branch '6.0.x'
# Conflicts:
#	framework-platform/framework-platform.gradle
#	spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProvider.java
2023-11-09 11:50:55 +01:00
Juergen Hoeller 1e78cc35e5 Log4jLog re-resolves ExtendedLogger on deserialization
This is necessary for compatibility with Log4J 2.21, analogous to the existing re-resolution in Spring's SLF4J adapter.

Closes gh-31582
2023-11-09 11:46:22 +01:00
Simon Baslé 42874178ce Polish RecordApplicationEvents javadoc: add note on captured threads
This commit clarifies that the annotation is used to capture events that
are fired from the test `Thread` or descendant threads, since the test
framework uses `InheritableThreadLocal` to store captured events now.

See gh-31079
See gh-30020
2023-11-09 11:07:53 +01:00
Juergen Hoeller 11fdb5ba17 Upgrade to Log4J 2.21.1, Tomcat 10.1.15, Jetty 11.0.18, Undertow 2.3.10, EclipseLink 3.0.4, Mockito 5.7 2023-11-09 10:53:47 +01:00
Juergen Hoeller 9957bb6918 Check for procedure vs function constants in CallMetaDataContext
Closes gh-31550
2023-11-09 10:52:51 +01:00
Juergen Hoeller 9414c2ddba Coordinated stop before destroy when ExecutorService not terminated yet
Closes gh-31549
2023-11-09 10:22:27 +01:00
Sébastien Deleuze 38724a1205 Fix RestClient generic type handling
For client side use case, the context class should be null,
consistently with what is done in HttpMessageConverterExtractor.

Closes gh-31574
2023-11-09 08:50:31 +01:00
Sam Brannen 55d13a0232 Document @⁠DisabledInAotMode & @[Enabled|Disabled]InNativeImage in ref docs
Closes gh-31437
Closes gh-31438
2023-11-08 16:22:35 +01:00
Sam Brannen a207e5e1b0 Ensure framework-docs can be imported into Eclipse IDE 2023-11-08 15:26:09 +01:00
Sam Brannen b7c579954c Merge branch '6.0.x'
# Conflicts:
#	spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java
2023-11-08 13:48:22 +01:00
lorenzsimon 6a7a0bddb7 Restore support for recursive annotations in Kotlin
This commit reinstates support for recursive annotations in Kotlin.

See gh-28012
See gh-28618
See gh-31400
Closes gh-31518
2023-11-08 13:28:28 +01:00
rstoyanchev ba4d459f81 Merge branch '6.0.x' 2023-11-08 11:47:18 +00:00
rstoyanchev 5c012bbb0c Set maxAge correctly when expiring WebSession
Closes gh-31214
2023-11-08 11:44:36 +00:00
Stéphane Nicoll d34b3c1a71 Merge branch '6.0.x' 2023-11-08 08:04:27 +01:00
Stéphane Nicoll e943058b18 Merge pull request #31571 from izeye
* pr/31571:
  Add Javadoc since to ProblemDetail.setProperties()

Closes gh-31571
2023-11-08 08:04:10 +01:00
Johnny Lim cafb38ad1d Add Javadoc since to ProblemDetail.setProperties()
See gh-31571
2023-11-08 08:02:57 +01:00
Sam Brannen 9759bf5964 Merge branch '6.0.x' 2023-11-07 17:13:46 +01:00
Sam Brannen e778d2e908 Polish duplicate key exception error code support for SAP HANA database for R2DBC
See gh-31554
2023-11-07 17:12:29 +01:00
Sam Brannen c5bcfc7682 Polish contribution
See gh-31554
2023-11-07 17:08:44 +01:00
Sam Brannen 5752e03d97 Polishing 2023-11-07 16:58:21 +01:00
Arjen Poutsma 5afb00d270 Merge branch '6.0.x' 2023-11-07 15:10:59 +01:00
Arjen Poutsma dc26d3b0ec Defer cleanup in DefaultServerWebExchange
This commit ensures that the multipartRead flag is read in a deferred
block, and is not evaluated too early.

Closes gh-31567
2023-11-07 15:00:20 +01:00
Arjen Poutsma 486503bd31 Buffer writes in JettyClientHttpRequest
This commit ensures that not every OutputStream.write gets written as a
separate chunk, by buffering the written data in a BufferedOutputStream.
In some cases, a large quantity of small writes would result in many
sent chunks.

Closes gh-31361
2023-11-07 14:02:01 +01:00
Arjen Poutsma 0839f5b749 Test form submissions
See gh-31361
2023-11-07 12:27:31 +01:00
Brian Clozel 888d8c15d9 Upgrade CI to Concourse Release Scripts 0.4.0 2023-11-07 11:20:57 +01:00
Brian Clozel dbb2d4f1d3 Extend JMS observation scope to error handling
Prior to this commit, the JMS listener observations would only cover the
actual listener invocation, but not the error handling phase. This means
that all errors (including the handled ones) are marked as errors in the
observation and that the observation is not current anymore when error
handling happens.

This commit aligns the behavior with the other Spring Framework
instrumentations and ensures that the error handling phase is fully part
of the observation recording.

Closes gh-31559
2023-11-07 11:18:03 +01:00
Sébastien Deleuze 9d6bda2b44 Register Hibernate @EmbeddableInstantiator reflection hints
This commit updates PersistenceManagedTypesBeanRegistrationAotProcessor
to register reflection hints for `@EmbeddableInstantiator` annotations
available as of Hibernate 6.0 when needed.

Related tests will be added in
https://github.com/spring-projects/spring-aot-smoke-tests.

Closes gh-31534
2023-11-07 11:04:43 +01:00
Stéphane Nicoll c30b3796f2 Merge branch '6.0.x' 2023-11-07 10:37:22 +01:00
Stéphane Nicoll 7f94c64b72 Merge pull request #31554 from baratrax
* pr/31554:
  Polish "Add SAP HANA duplicate key exception error code"
  Add SAP HANA duplicate key exception error code

Closes gh-31554
2023-11-07 10:37:14 +01:00
Stéphane Nicoll 50e55d5219 Polish "Add SAP HANA duplicate key exception error code"
See gh-31554
2023-11-07 10:33:14 +01:00
Fabrizio De Felice fcd4ba2f1f Add SAP HANA duplicate key exception error code
See gh-31554
2023-11-07 10:27:51 +01:00
wakingrufus 8c77e398b6 Add extension for PropertyResolver#getProperty(String, Class<T>, T)
Allows Kotlin caller to get a non-nullable property,
using a default value when the property is not set.
A method already exists on PropertyResolver which does this,
but takes a Class parameter.
The extension method can eliminate the parameter via reified type,
similar to the other extension methods which exist already.

Closes gh-31523
2023-11-06 15:14:16 +01:00
Sébastien Deleuze 34e5512ff6 Enable PropertyResolverExtensionsKotlinTests
Closes gh-31553
2023-11-06 15:14:16 +01:00
Sam Brannen cd503e3af6 Upgrade to JUnit 5.10.1
Closes gh-31558
2023-11-06 14:57:31 +01:00
rstoyanchev 5df6e8825d Polishing in CookieWebSessionIdResolver
See gh-31214
2023-11-06 11:31:39 +00:00
Brian Clozel 86bb8a015b Document that "error" key is preferred in observations
This commit documents that the "error" key in Micrometer Observations
should be preferred over the legacy "exception" one. Right now the
information is duplicated but we might remove the deprecated one in the
future.

Closes gh-31514
2023-11-06 11:52:27 +01:00
Arjen Poutsma efb93ca109 Fix bug in calculation of maximum form part size
See gh-31343
2023-11-06 11:20:18 +01:00
Sam Brannen b3a6dbaab3 Polishing 2023-11-04 14:45:09 +01:00
Sam Brannen ed49f86dfb Remove obsolete code in ConcurrentReferenceHashMapTests 2023-11-04 14:38:30 +01:00
Sam Brannen 12078e6ec5 Ensure Java runtime container is always properly configured in Eclipse 2023-11-04 14:32:34 +01:00
rstoyanchev 654e822676 Fix Javadoc link 2023-11-03 14:38:53 +00:00
Stéphane Nicoll 24aa6163f0 Polish 2023-11-03 12:10:01 +01:00
rstoyanchev 30e4a0a300 Polishing as a result of discussion under gh-31529 2023-11-02 16:18:25 +00:00
Arjen Poutsma 6dd93d4d85 Allow repeatable writes in HttpMessageConverter
This commit ensures that the StreamingHttpOutputMessage.Body.repeatable
flag is set in message converters for bodies that can be written
repeatedly.

Closes gh-31516
See gh-31449
2023-11-02 15:51:36 +01:00
Stéphane Nicoll ab316d9bc8 Merge branch '6.0.x' 2023-11-02 15:41:18 +01:00
Stéphane Nicoll e73107341c Document that pertypewithin is supported by Spring AOT
Closes gh-25887
2023-11-02 15:39:45 +01:00
Stéphane Nicoll 27b5813591 Merge pull request #31542 from Young-Zen
* pr/31542:
  Fix link in Javadoc of ConfigurableMockMvcBuilder

Closes gh-31542
2023-11-02 14:26:46 +01:00
yanghaojia fe14c85ada Fix link in Javadoc of ConfigurableMockMvcBuilder
See gh-31542
2023-11-02 14:25:35 +01:00
rstoyanchev 4da2440f63 Polishing
Closes gh-31530
2023-11-02 11:32:03 +00:00
Seth Kuipers 9bbe3aa52a Expand support for adapting container type violations
See gh-31530
2023-11-02 11:32:03 +00:00
rstoyanchev f16122d533 Polishing and minor refactoring
Closes gh-31202
2023-11-02 11:32:03 +00:00
Carl-Eric Menzel d54a694f5a Add handler for unknown status codes
See gh-31202
2023-11-02 11:32:03 +00:00
Stéphane Nicoll 9c5dcad0e5 Polish 2023-11-02 11:45:07 +01:00
Stéphane Nicoll b05b5902c0 Upgrade to Kotlin 1.9.20
Closes gh-31539
2023-11-02 09:42:42 +01:00
Stéphane Nicoll 1d38d649fa Relax test that used to rely on localized message
This reverts commit 93206c3f6e and updates
the related test to only rely on the fact the compiler fails. Relying
on a message will not work and the status code can be implementation
independent according to its javadoc.

Closes gh-31536
2023-11-01 16:33:44 +01:00
Sam Brannen a5841fede5 Polishing 2023-11-01 16:23:18 +01:00
rstoyanchev 2a5653c7d6 Merge branch '6.0.x' 2023-11-01 13:26:34 +00:00
rstoyanchev f8a33cd66e Remove outdated reference to Netty in rest-clients section
Closes gh-31526
2023-11-01 13:25:16 +00:00
Stéphane Nicoll 115a46c5e8 Polish 2023-10-31 17:58:13 +01:00
Stéphane Nicoll 199d5e5f37 Polish 2023-10-31 13:02:01 +01:00
Sam Brannen 3d248607dc Update copyright headers 2023-10-30 15:21:02 +01:00
Sam Brannen 574c57739e Further polishing 2023-10-30 15:10:13 +01:00
Johnny Lim 2b750926c3 Polishing
Closes gh-31522
2023-10-30 15:05:20 +01:00
Sam Brannen 8a2acbeac1 Merge branch '6.0.x' 2023-10-30 13:32:08 +01:00
CroBurnt f088d4a05b Fix link text in @⁠Transactional section of ref docs
Closes gh-31519
2023-10-30 13:29:30 +01:00
Sam Brannen 8770544769 Remove classpath entries for non-existent libraries added by mrjar plugin
The me.champeau.mrjar Gradle plugin causes non-existent libraries to be
added to the Eclipse classpath, such as:

<classpathentry kind="lib" path="/workspaces/spring-framework/spring-core/build/classes/kotlin/java21">
  <attributes>
    <attribute name="gradle_used_by_scope" value="java21"/>
    <attribute name="test" value="true"/>
  </attributes>
</classpathentry>

This results in build errors in Eclipse IDE like the following.

Project 'spring-core' is missing required library:
  '/workspaces/spring-framework/spring-core/build/classes/kotlin/java21'

This commit filters those and removes them.
2023-10-29 12:03:40 +01:00
Sam Brannen fc77172d67 Remove recursive project dependencies from Eclipse classpath 2023-10-29 11:25:53 +01:00
Sam Brannen de74520286 Upgrade to JMH plugin 0.7.2
Closes gh-31517
2023-10-29 11:09:48 +01:00
Sam Brannen cb9382d986 Polishing 2023-10-29 10:16:32 +01:00
Sam Brannen c7bb981db1 Ensure it's possible to extend ControlFlowPointcut with RegEx support
Commit d3fba6d49b introduced built-in pattern matching support for
method names in ControlFlowPointcut; however, it was still cumbersome
to extend ControlFlowPointcut with support for regular expressions
instead of simple pattern matching.

To address that, this commit introduces a variant of isMatch() that
accepts the pattern index instead of the pre-resolved method name
pattern. The default implementation retrieves the method name pattern
from the methodNamePatterns field and delegates to isMatch(String, String).

Subclasses can override the new isMatch(String, int) method to support
regular expressions, as can be seen in the example
RegExControlFlowPointcut class in ControlFlowPointcutTests.

See gh-31435
2023-10-28 16:30:02 +02:00
Sam Brannen 7f1beb0140 Polishing 2023-10-28 16:24:59 +02:00
Stéphane Nicoll 1762bf4a60 Fix code generation for null indexed argument value
This commit fixes code generation when an indexed constructor argument
value is null as the method is overloaded and need the value to be
cast to `Object`.

Closes gh-31508
2023-10-28 10:52:26 +02:00
Sam Brannen d3fba6d49b Support pattern matching for method names in ControlFlowPointcut
Prior to this commit, ControlFlowPointcut supported a single method
name which was matched exactly. Although it was possible to extend
ControlFlowPointcut to add support for pattern matching, it was a bit
cumbersome.

To address that, this commit introduces built-in pattern matching
support for method names in ControlFlowPointcut, analogous to the
pattern matching support in NameMatchMethodPointcut.

Specifically, a user can provide one or more method name patterns, and
the patterns will be matched against candidate method names using OR
semantics.

By default, the matching algorithm delegates to
PatternMatchUtils.simpleMatch(), but this can be overridden in
subclasses by overriding the new protected isMatch() method.

Closes gh-31435
2023-10-27 18:01:36 +02:00
Sam Brannen c5333a0a68 Polishing 2023-10-27 17:44:53 +02:00
Sam Brannen 44b98c6a8b Polish PatternMatchUtils[Tests] 2023-10-27 17:44:44 +02:00
234 changed files with 3252 additions and 1216 deletions
+2 -2
View File
@@ -7,7 +7,7 @@ plugins {
id 'com.github.ben-manes.versions' version '0.49.0'
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
id 'de.undercouch.download' version '5.4.0'
id 'me.champeau.jmh' version '0.7.1' apply false
id 'me.champeau.jmh' version '0.7.2' apply false
id 'me.champeau.mrjar' version '0.1.1'
}
@@ -102,7 +102,7 @@ configure([rootProject] + javaProjects) { project ->
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
// See https://github.com/spring-projects/spring-framework/issues/27497
//
// "https://junit.org/junit5/docs/5.10.0/api/",
// "https://junit.org/junit5/docs/5.10.1/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("10.12.4");
checkstyle.setToolVersion("10.12.5");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
+1 -1
View File
@@ -4,7 +4,7 @@ image_resource:
type: registry-image
source:
repository: springio/concourse-release-scripts
tag: '0.4.0-SNAPSHOT'
tag: '0.4.0'
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
+4 -2
View File
@@ -27,7 +27,8 @@ javadoc {
author = true
header = rootProject.description
use = true
overview = "framework-docs/src/docs/api/overview.html"
overview = "$rootProject.rootDir/framework-docs/src/docs/api/overview.html"
destinationDir = file("${project.buildDir}/docs/javadoc-api")
splitIndex = true
links(rootProject.ext.javadocLinks)
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
@@ -51,7 +52,8 @@ rootProject.tasks.dokkaHtmlMultiModule.configure {
tasks.named("javadoc")
}
moduleName.set("spring-framework")
outputDirectory.set(project.file("$buildDir/docs/kdoc"))
outputDirectory.set(project.file("$buildDir/docs/kdoc-api"))
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
}
/**
+1
View File
@@ -6,6 +6,7 @@ plugins {
description = "Spring Framework Docs"
apply from: "${rootDir}/gradle/ide.gradle"
apply from: "${rootDir}/gradle/publications.gradle"
antora {
+1
View File
@@ -182,6 +182,7 @@
***** xref:testing/annotations/integration-spring/annotation-sqlconfig.adoc[]
***** xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[]
***** xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[]
***** xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[]
**** xref:testing/annotations/integration-junit4.adoc[]
**** xref:testing/annotations/integration-junit-jupiter.adoc[]
**** xref:testing/annotations/integration-meta.adoc[]
@@ -6,8 +6,8 @@ it until later.
By default, there is a single instance of each aspect within the application
context. AspectJ calls this the singleton instantiation model. It is possible to define
aspects with alternate lifecycles. Spring supports AspectJ's `perthis` and `pertarget`
instantiation models; `percflow`, `percflowbelow`, and `pertypewithin` are not currently
aspects with alternate lifecycles. Spring supports AspectJ's `perthis`, `pertarget`, and
`pertypewithin` instantiation models; `percflow` and `percflowbelow` are not currently
supported.
You can declare a `perthis` aspect by specifying a `perthis` clause in the `@Aspect`
@@ -76,7 +76,7 @@ Kotlin::
Used at the class level as above, the annotation indicates a default for all methods of
the declaring class (as well as its subclasses). Alternatively, each method can be
annotated individually. See xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[null] for
annotated individually. See xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[method visibility] for
further details on which methods Spring considers transactional. Note that a class-level
annotation does not apply to ancestor classes up the class hierarchy; in such a scenario,
inherited methods need to be locally redeclared in order to participate in a
@@ -109,7 +109,8 @@ By default, the following `KeyValues` are created:
|Name | Description
|`code.function` _(required)_|Name of Java `Method` that is scheduled for execution.
|`code.namespace` _(required)_|Canonical name of the class of the bean instance that holds the scheduled method.
|`exception` _(required)_|Name of the exception thrown during the execution, or `"none"` if no exception happened.
|`error` _(required)_|Class name of the exception thrown during the execution, or `"none"` if no exception happened.
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|`outcome` _(required)_|Outcome of the method execution. Can be `"SUCCESS"`, `"ERROR"` or `"UNKNOWN"` (if for example the operation was cancelled during execution).
|===
@@ -134,7 +135,8 @@ By default, both observations share the same set of possible `KeyValues`:
[cols="a,a"]
|===
|Name | Description
|`exception` |Class name of the exception thrown during the messaging operation (or "none").
|`error` |Class name of the exception thrown during the messaging operation (or "none").
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|`messaging.destination.temporary` _(required)_|Whether the destination is a `TemporaryQueue` or `TemporaryTopic` (values: `"true"` or `"false"`).
|`messaging.operation` _(required)_|Name of JMS operation being performed (values: `"publish"` or `"process"`).
|===
@@ -199,7 +201,7 @@ include-code::./UserController[]
NOTE: Because the instrumentation is done at the Servlet Filter level, the observation scope only covers the filters ordered after this one as well as the handling of the request.
Typically, Servlet container error handling is performed at a lower level and won't have any active observation or span.
For this use case, a container-specific implementation is required, such as a `org.apache.catalina.Valve` for Tomcat; this is outside of the scope of this project.
For this use case, a container-specific implementation is required, such as a `org.apache.catalina.Valve` for Tomcat; this is outside the scope of this project.
By default, the following `KeyValues` are created:
@@ -207,8 +209,9 @@ By default, the following `KeyValues` are created:
[cols="a,a"]
|===
|Name | Description
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|`error` _(required)_|Class name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
@@ -244,8 +247,9 @@ By default, the following `KeyValues` are created:
[cols="a,a"]
|===
|Name | Description
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|`error` _(required)_|Class name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
@@ -278,12 +282,13 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
[cols="a,a"]
|===
|Name | Description
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|`client.name` _(required)_|Client name derived from the request URI host.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`error` _(required)_|Class name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|===
.High cardinality Keys
@@ -310,7 +315,8 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
|`client.name` _(required)_|Client name derived from the request URI host.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`error` _(required)_|Class name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|===
.High cardinality Keys
@@ -333,12 +339,13 @@ Instrumentation uses the `org.springframework.web.reactive.function.client.Clien
[cols="a,a"]
|===
|Name | Description
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|`client.name` _(required)_|Client name derived from the request URI host.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`error` _(required)_|Class name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|===
.High cardinality Keys
@@ -354,18 +361,18 @@ Instrumentation uses the `org.springframework.web.reactive.function.client.Clien
Spring Framework does not contribute Observations for xref:core/beans/context-introduction.adoc#context-functionality-events-annotation[`@EventListener` calls],
as they don't have the right semantics for such instrumentation.
By default, event publication and processing is done synchronously and on the same Thread.
By default, event publication and processing are done synchronously and on the same thread.
This means that during the execution of that task, the ThreadLocals and logging context will be the same as the event publisher.
If the application configures globally a custom `ApplicationEventMulticaster` with a strategy that schedules event processing on different threads, this is no longer true.
All `@EventListener` methods will be processed on a different thread, outstide of the main event publication thread.
In these cases, the https://micrometer.io/docs/contextPropagation[Micrometer Context Propagation library] can help propagating such values and better correlate the processing of the events.
If the application globally configures a custom `ApplicationEventMulticaster` with a strategy that schedules event processing on different threads, this is no longer true.
All `@EventListener` methods will be processed on a different thread, outside the main event publication thread.
In these cases, the https://micrometer.io/docs/contextPropagation[Micrometer Context Propagation library] can help propagate such values and better correlate the processing of the events.
The application can configure the chosen `TaskExecutor` to use a `ContextPropagatingTaskDecorator` that decorates tasks and propagates context.
For this to work, the `io.micrometer:context-propagation` library must be present on the classpath:
include-code::./ApplicationEventsConfiguration[]
Similarly, if that asynchronous choice is made locally for each `@EventListener` annotated method, by adding an `@Async` method to it,
Similarly, if that asynchronous choice is made locally for each `@EventListener` annotated method, by adding `@Async` to it,
you can choose a `TaskExecutor` that propagates context by referring to it by its qualifier.
Given the following `TaskExecutor` bean definition, configured with the dedicated task decorator:
@@ -248,7 +248,7 @@ For example, declaring `List<Foo>` in Kotlin is conceptually equivalent to `java
`kotlin.collections.List` is declared as
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/[`interface List<out E> : kotlin.collections.Collection<E>`].
This needs to be taken in account by using the `out` Kotlin keyword on generic types when using Java classes,
This needs to be taken into account by using the `out` Kotlin keyword on generic types when using Java classes,
for example when writing a `org.springframework.core.convert.converter.Converter` from a Kotlin type to a Java type.
[source,kotlin,indent=0]
@@ -11,6 +11,7 @@ xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupite
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[`@NestedTestConfiguration`]
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-enabledif[`@EnabledIf`]
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-disabledif[`@DisabledIf`]
* xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`]
[[integration-testing-annotations-junit-jupiter-springjunitconfig]]
== `@SpringJUnitConfig`
@@ -27,4 +27,5 @@ Spring's testing annotations include the following:
* xref:testing/annotations/integration-spring/annotation-sqlconfig.adoc[`@SqlConfig`]
* xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[`@SqlMergeMode`]
* xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[`@SqlGroup`]
* xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`]
@@ -0,0 +1,20 @@
[[spring-testing-annotation-disabledinaotmode]]
= `@DisabledInAotMode`
`@DisabledInAotMode` signals that an annotated test class is disabled in Spring AOT
(ahead-of-time) mode, which means that the `ApplicationContext` for the test class will
not be processed for AOT optimizations at build time.
If a test class is annotated with `@DisabledInAotMode`, all other test classes which
specify configuration to load the same `ApplicationContext` must also be annotated with
`@DisabledInAotMode`. Failure to annotate all such test classes will result in an
exception, either at build time or run time.
When used with JUnit Jupiter based tests, `@DisabledInAotMode` also signals that the
annotated test class or test method is disabled when running the test suite in Spring AOT
mode. When applied at the class level, all test methods within that class will be
disabled. In this sense, `@DisabledInAotMode` has semantics similar to those of JUnit
Jupiter's `@DisabledInNativeImage` annotation.
For details on AOT support specific to integration tests, see
xref:testing/testcontext-framework/aot.adoc[Ahead of Time Support for Tests].
@@ -19,6 +19,15 @@ following features.
use an AOT-optimized `ApplicationContext` that participates transparently with the
xref:testing/testcontext-framework/ctx-management/caching.adoc[context cache].
All tests are enabled in AOT mode by default. However, you can selectively disable an
entire test class or individual test method in AOT mode by annotating it with
xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`].
When using JUnit Jupiter, you may selectively enable or disable tests in a GraalVM native
image via Jupiter's `@EnabledInNativeImage` and `@DisabledInNativeImage` annotations.
Note that `@DisabledInAotMode` also disables the annotated test class or test method when
running within a GraalVM native image, analogous to JUnit Jupiter's
`@DisabledInNativeImage` annotation.
[TIP]
====
By default, if an error is encountered during build-time AOT processing, an exception
@@ -95,13 +95,22 @@ In addition to the aforementioned mechanisms for running SQL scripts programmati
you can declaratively configure SQL scripts in the Spring TestContext Framework.
Specifically, you can declare the `@Sql` annotation on a test class or test method to
configure individual SQL statements or the resource paths to SQL scripts that should be
run against a given database before or after an integration test method. Support for
`@Sql` is provided by the `SqlScriptsTestExecutionListener`, which is enabled by default.
run against a given database before or after an integration test class or test method.
Support for `@Sql` is provided by the `SqlScriptsTestExecutionListener`, which is enabled
by default.
NOTE: Method-level `@Sql` declarations override class-level declarations by default. As
of Spring Framework 5.2, however, this behavior may be configured per test class or per
test method via `@SqlMergeMode`. See
xref:testing/testcontext-framework/executing-sql.adoc#testcontext-executing-sql-declaratively-script-merging[Merging and Overriding Configuration with `@SqlMergeMode`] for further details.
[NOTE]
====
Method-level `@Sql` declarations override class-level declarations by default, but this
behavior may be configured per test class or per test method via `@SqlMergeMode`. See
xref:testing/testcontext-framework/executing-sql.adoc#testcontext-executing-sql-declaratively-script-merging[Merging and Overriding Configuration with `@SqlMergeMode`]
for further details.
However, this does not apply to class-level declarations configured for the
`BEFORE_TEST_CLASS` or `AFTER_TEST_CLASS` execution phases. Such declarations cannot be
overridden, and the corresponding scripts and statements will be executed once per class
in addition to any method-level scripts and statements.
====
[[testcontext-executing-sql-declaratively-script-resources]]
=== Path Resource Semantics
@@ -174,17 +183,25 @@ script, depending on where `@Sql` is declared. If a default cannot be detected,
defined in the class `com.example.MyTest`, the corresponding default script is
`classpath:com/example/MyTest.testMethod.sql`.
[[testcontext-executing-sql-declaratively-logging]]
=== Logging SQL Scripts and Statements
If you want to see which SQL scripts are being executed, set the
`org.springframework.test.context.jdbc` logging category to `DEBUG`.
If you want to see which SQL statements are being executed, set the
`org.springframework.jdbc.datasource.init` logging category to `DEBUG`.
[[testcontext-executing-sql-declaratively-multiple-annotations]]
=== Declaring Multiple `@Sql` Sets
If you need to configure multiple sets of SQL scripts for a given test class or test
method but with different syntax configuration, different error handling rules, or
different execution phases per set, you can declare multiple instances of `@Sql`. With
Java 8, you can use `@Sql` as a repeatable annotation. Otherwise, you can use the
`@SqlGroup` annotation as an explicit container for declaring multiple instances of
`@Sql`.
different execution phases per set, you can declare multiple instances of `@Sql`. You can
either use `@Sql` as a repeatable annotation, or you can use the `@SqlGroup` annotation
as an explicit container for declaring multiple instances of `@Sql`.
The following example shows how to use `@Sql` as a repeatable annotation with Java 8:
The following example shows how to use `@Sql` as a repeatable annotation:
[tabs]
======
@@ -204,7 +221,12 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin
@Test
@Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
fun userTest() {
// run code that uses the test schema and test data
}
----
======
@@ -212,9 +234,8 @@ In the scenario presented in the preceding example, the `test-schema.sql` script
different syntax for single-line comments.
The following example is identical to the preceding example, except that the `@Sql`
declarations are grouped together within `@SqlGroup`. With Java 8 and above, the use of
`@SqlGroup` is optional, but you may need to use `@SqlGroup` for compatibility with
other JVM languages such as Kotlin.
declarations are grouped together within `@SqlGroup`. The use of `@SqlGroup` is optional,
but you may need to use `@SqlGroup` for compatibility with other JVM languages.
[tabs]
======
@@ -239,7 +260,8 @@ Kotlin::
@Test
@SqlGroup(
Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
Sql("/test-user-data.sql"))
Sql("/test-user-data.sql")
)
fun userTest() {
// Run code that uses the test schema and test data
}
@@ -249,10 +271,10 @@ Kotlin::
[[testcontext-executing-sql-declaratively-script-execution-phases]]
=== Script Execution Phases
By default, SQL scripts are run before the corresponding test method. However, if
you need to run a particular set of scripts after the test method (for example, to clean
up database state), you can use the `executionPhase` attribute in `@Sql`, as the
following example shows:
By default, SQL scripts are run before the corresponding test method. However, if you
need to run a particular set of scripts after the test method (for example, to clean up
database state), you can set the `executionPhase` attribute in `@Sql` to
`AFTER_TEST_METHOD`, as the following example shows:
[tabs]
======
@@ -281,12 +303,11 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@Test
@SqlGroup(
Sql("create-test-data.sql",
config = SqlConfig(transactionMode = ISOLATED)),
Sql("delete-test-data.sql",
config = SqlConfig(transactionMode = ISOLATED),
executionPhase = AFTER_TEST_METHOD))
@Sql("create-test-data.sql",
config = SqlConfig(transactionMode = ISOLATED))
@Sql("delete-test-data.sql",
config = SqlConfig(transactionMode = ISOLATED),
executionPhase = AFTER_TEST_METHOD)
fun userTest() {
// run code that needs the test data to be committed
// to the database outside of the test's transaction
@@ -294,9 +315,60 @@ Kotlin::
----
======
Note that `ISOLATED` and `AFTER_TEST_METHOD` are statically imported from
NOTE: `ISOLATED` and `AFTER_TEST_METHOD` are statically imported from
`Sql.TransactionMode` and `Sql.ExecutionPhase`, respectively.
As of Spring Framework 6.1, it is possible to run a particular set of scripts before or
after the test class by setting the `executionPhase` attribute in a class-level `@Sql`
declaration to `BEFORE_TEST_CLASS` or `AFTER_TEST_CLASS`, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@SpringJUnitConfig
@Sql(scripts = "/test-schema.sql", executionPhase = BEFORE_TEST_CLASS)
class DatabaseTests {
@Test
void emptySchemaTest() {
// run code that uses the test schema without any test data
}
@Test
@Sql("/test-user-data.sql")
void userTest() {
// run code that uses the test schema and test data
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@SpringJUnitConfig
@Sql("/test-schema.sql", executionPhase = BEFORE_TEST_CLASS)
class DatabaseTests {
@Test
fun emptySchemaTest() {
// run code that uses the test schema without any test data
}
@Test
@Sql("/test-user-data.sql")
fun userTest() {
// run code that uses the test schema and test data
}
}
----
======
NOTE: `BEFORE_TEST_CLASS` is statically imported from `Sql.ExecutionPhase`.
[[testcontext-executing-sql-declaratively-script-configuration]]
=== Script Configuration with `@SqlConfig`
@@ -324,7 +396,7 @@ individual attributes in {api-spring-framework}/test/context/jdbc/Sql.html[`@Sql
{api-spring-framework}/test/context/jdbc/SqlConfig.html[`@SqlConfig`] for details.
[[testcontext-executing-sql-declaratively-tx]]
*Transaction management for `@Sql`*
==== Transaction management for `@Sql`
By default, the `SqlScriptsTestExecutionListener` infers the desired transaction
semantics for scripts configured by using `@Sql`. Specifically, SQL scripts are run
@@ -0,0 +1,2 @@
# All Modules
_See also the <a href="../javadoc-api/" target="_blank">Java API documentation (Javadoc)</a>._
+4 -1
View File
@@ -1,7 +1,10 @@
<html>
<body>
<p>
This is the public API documentation for the <a href="https://github.com/spring-projects/spring-framework" target="_top">Spring Framework</a>.
This is the public Java API documentation (Javadoc) for the <a href="https://github.com/spring-projects/spring-framework" target="_top">Spring Framework</a>.
</p>
<p><em>
See also the <a href="../kdoc-api/" target="_blank">Kotlin API documentation (KDoc)</a>.
</em></p>
</body>
</html>
+34 -36
View File
@@ -8,30 +8,30 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.15.2"))
api(platform("io.micrometer:micrometer-bom:1.12.0-RC1"))
api(platform("io.netty:netty-bom:4.1.100.Final"))
api(platform("io.micrometer:micrometer-bom:1.12.0"))
api(platform("io.netty:netty-bom:4.1.101.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2023.0.0-RC1"))
api(platform("io.projectreactor:reactor-bom:2023.0.0"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.15"))
api(platform("org.apache.logging.log4j:log4j-bom:2.20.0"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.2"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.2"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.3"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.3"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0"))
api(platform("org.junit:junit-bom:5.10.0"))
api(platform("org.mockito:mockito-bom:5.6.0"))
api(platform("org.junit:junit-bom:5.10.1"))
api(platform("org.mockito:mockito-bom:5.7.0"))
constraints {
api("com.fasterxml:aalto-xml:1.3.2")
api("com.fasterxml.woodstox:woodstox-core:6.5.1")
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
api("com.github.librepdf:openpdf:1.3.30")
api("com.github.librepdf:openpdf:1.3.33")
api("com.google.code.findbugs:findbugs:3.0.1")
api("com.google.code.findbugs:jsr305:3.0.2")
api("com.google.code.gson:gson:2.10.1")
api("com.google.protobuf:protobuf-java-util:3.24.4")
api("com.h2database:h2:2.2.220")
api("com.google.protobuf:protobuf-java-util:3.25.0")
api("com.h2database:h2:2.2.224")
api("com.jayway.jsonpath:json-path:2.8.0")
api("com.rometools:rome:1.19.0")
api("com.squareup.okhttp3:mockwebserver:3.14.9")
@@ -43,20 +43,20 @@ dependencies {
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
api("com.thoughtworks.qdox:qdox:2.0.3")
api("com.thoughtworks.xstream:xstream:1.4.20")
api("commons-io:commons-io:2.11.0")
api("commons-io:commons-io:2.15.0")
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
api("io.micrometer:context-propagation:1.1.0-RC1")
api("io.micrometer:context-propagation:1.1.0")
api("io.mockk:mockk:1.13.4")
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
api("io.projectreactor.tools:blockhound:1.0.8.RELEASE")
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
api("io.reactivex.rxjava3:rxjava:3.1.6")
api("io.smallrye.reactive:mutiny:1.9.0")
api("io.undertow:undertow-core:2.3.8.Final")
api("io.undertow:undertow-servlet:2.3.8.Final")
api("io.undertow:undertow-websockets-jsr:2.3.8.Final")
api("io.reactivex.rxjava3:rxjava:3.1.8")
api("io.smallrye.reactive:mutiny:1.10.0")
api("io.undertow:undertow-core:2.3.10.Final")
api("io.undertow:undertow-servlet:2.3.10.Final")
api("io.undertow:undertow-websockets-jsr:2.3.10.Final")
api("io.vavr:vavr:0.10.4")
api("jakarta.activation:jakarta.activation-api:2.0.1")
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
@@ -90,22 +90,22 @@ dependencies {
api("net.sf.jopt-simple:jopt-simple:5.0.4")
api("net.sourceforge.htmlunit:htmlunit:2.70.0")
api("org.apache-extras.beanshell:bsh:2.0b6")
api("org.apache.activemq:activemq-broker:5.17.4")
api("org.apache.activemq:activemq-kahadb-store:5.17.4")
api("org.apache.activemq:activemq-stomp:5.17.4")
api("org.apache.activemq:artemis-junit-5:2.29.0")
api("org.apache.activemq:artemis-jakarta-client:2.29.0")
api("org.apache.activemq:activemq-broker:5.17.6")
api("org.apache.activemq:activemq-kahadb-store:5.17.6")
api("org.apache.activemq:activemq-stomp:5.17.6")
api("org.apache.activemq:artemis-jakarta-client:2.31.2")
api("org.apache.activemq:artemis-junit-5:2.31.2")
api("org.apache.commons:commons-pool2:2.9.0")
api("org.apache.derby:derby:10.16.1.1")
api("org.apache.derby:derbyclient:10.16.1.1")
api("org.apache.derby:derbytools:10.16.1.1")
api("org.apache.httpcomponents.client5:httpclient5:5.2.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.2")
api("org.apache.poi:poi-ooxml:5.2.3")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.14")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.14")
api("org.apache.tomcat:tomcat-util:10.1.14")
api("org.apache.tomcat:tomcat-websocket:10.1.14")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.3")
api("org.apache.poi:poi-ooxml:5.2.4")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.15")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.15")
api("org.apache.tomcat:tomcat-util:10.1.15")
api("org.apache.tomcat:tomcat-websocket:10.1.15")
api("org.aspectj:aspectjrt:1.9.20.1")
api("org.aspectj:aspectjtools:1.9.20.1")
api("org.aspectj:aspectjweaver:1.9.20.1")
@@ -115,24 +115,22 @@ dependencies {
api("org.codehaus.jettison:jettison:1.5.4")
api("org.crac:crac:1.4.0")
api("org.dom4j:dom4j:2.1.4")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.0")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.1")
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
api("org.eclipse:yasson:2.0.4")
api("org.ehcache:ehcache:3.10.8")
api("org.ehcache:jcache:1.0.1")
api("org.freemarker:freemarker:2.3.32")
// Substitute for "javax.management:jmxremote_optional:1.0.1_04" which
// is not available on Maven Central
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
api("org.glassfish:jakarta.el:4.0.2")
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
api("org.graalvm.sdk:graal-sdk:22.3.1")
api("org.hamcrest:hamcrest:2.2")
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
api("org.hibernate:hibernate-validator:7.0.5.Final")
api("org.hsqldb:hsqldb:2.7.2")
api("org.javamoney:moneta:1.4.2")
api("org.jruby:jruby:9.4.3.0")
api("org.jruby:jruby:9.4.5.0")
api("org.junit.support:testng-engine:1.0.4")
api("org.mozilla:rhino:1.7.14")
api("org.ogce:xpp3:1.1.6")
@@ -144,9 +142,9 @@ dependencies {
api("org.slf4j:slf4j-api:2.0.9")
api("org.testng:testng:7.8.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-core:0.53")
api("org.webjars:webjars-locator-core:0.55")
api("org.xmlunit:xmlunit-assertj:2.9.1")
api("org.xmlunit:xmlunit-matchers:2.9.1")
api("org.yaml:snakeyaml:2.0")
api("org.yaml:snakeyaml:2.2")
}
}
+2 -2
View File
@@ -1,10 +1,10 @@
version=6.1.0-SNAPSHOT
version=6.1.0
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
org.gradle.parallel=true
kotlinVersion=1.9.20-RC2
kotlinVersion=1.9.20
kotlin.jvm.target.validation.mode=ignore
kotlin.stdlib.default.dependency=false
+13 -5
View File
@@ -1,3 +1,4 @@
import org.gradle.plugins.ide.eclipse.model.Library
import org.gradle.plugins.ide.eclipse.model.ProjectDependency
import org.gradle.plugins.ide.eclipse.model.SourceFolder
@@ -6,6 +7,7 @@ apply plugin: 'eclipse'
eclipse.jdt {
sourceCompatibility = 17
targetCompatibility = 17
javaRuntimeName = "JavaSE-17"
}
// Replace classpath entries with project dependencies (GRADLE-1116)
@@ -60,12 +62,18 @@ eclipse.classpath.file.whenMerged {
}
}
// Ensure that JMH sources and resources are treated as test classpath entries
// so that they can see test fixtures.
// https://github.com/melix/jmh-gradle-plugin/issues/157
// Remove recursive project dependencies
eclipse.classpath.file.whenMerged {
entries.findAll { it.path =~ /src\/jmh\/(java|kotlin|resources)/ }.each {
it.entryAttributes['test'] = 'true'
entries.findAll { it instanceof ProjectDependency && it.path == ('/' + project.name) }.each {
entries.remove(it)
}
}
// Remove classpath entries for non-existent libraries added by the me.champeau.mrjar
// plugin, such as "spring-core/build/classes/kotlin/java21".
eclipse.classpath.file.whenMerged {
entries.findAll { it instanceof Library && !file(it.path).exists() }.each {
entries.remove(it)
}
}
@@ -87,7 +87,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
* We need to detect this as "code-style" AspectJ aspects should not be
* interpreted by Spring AOP.
*/
private boolean compiledByAjc(Class<?> clazz) {
static boolean compiledByAjc(Class<?> clazz) {
// The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
// annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
// an implementation detail of the AspectJ compiler.
@@ -0,0 +1,66 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.aspectj.annotation;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.util.ClassUtils;
/**
* An AOT {@link BeanRegistrationAotProcessor} that detects the presence of
* classes compiled with AspectJ and adds the related required field hints.
*
* @author Sebastien Deleuze
* @since 6.1
*/
class AspectJAdvisorBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
private static final boolean aspectjPresent = ClassUtils.isPresent("org.aspectj.lang.annotation.Pointcut",
AspectJAdvisorBeanRegistrationAotProcessor.class.getClassLoader());
@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
if (aspectjPresent) {
Class<?> beanClass = registeredBean.getBeanClass();
if (AbstractAspectJAdvisorFactory.compiledByAjc(beanClass)) {
return new AspectJAdvisorContribution(beanClass);
}
}
return null;
}
private static class AspectJAdvisorContribution implements BeanRegistrationAotContribution {
private final Class<?> beanClass;
public AspectJAdvisorContribution(Class<?> beanClass) {
this.beanClass = beanClass;
}
@Override
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
generationContext.getRuntimeHints().reflection().registerType(this.beanClass, MemberCategory.DECLARED_FIELDS);
}
}
}
@@ -40,11 +40,12 @@ import org.springframework.util.ClassUtils;
*/
class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
private static final boolean aspectJPresent = ClassUtils.isPresent(
"org.aspectj.lang.annotation.Pointcut", AspectJBeanFactoryInitializationAotProcessor.class.getClassLoader());
private static final boolean aspectJPresent = ClassUtils.isPresent("org.aspectj.lang.annotation.Pointcut",
AspectJBeanFactoryInitializationAotProcessor.class.getClassLoader());
@Nullable
@Override
@Nullable
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
if (aspectJPresent) {
return AspectDelegate.processAheadOfTime(beanFactory);
@@ -52,6 +53,7 @@ class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitial
return null;
}
/**
* Inner class to avoid a hard dependency on AspectJ at runtime.
*/
@@ -63,7 +65,6 @@ class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitial
List<Advisor> advisors = builder.buildAspectJAdvisors();
return (advisors.isEmpty() ? null : new AspectContribution(advisors));
}
}
@@ -84,7 +85,6 @@ class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitial
}
}
}
}
}
@@ -18,6 +18,9 @@ package org.springframework.aop.support;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.aop.ClassFilter;
@@ -25,11 +28,15 @@ import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PatternMatchUtils;
/**
* Pointcut and method matcher for use as a simple <b>cflow</b>-style pointcut.
*
* <p>Each configured method name pattern can be an exact method name or a
* pattern (see {@link #isMatch(String, String)} for details on the supported
* pattern styles).
*
* <p>Note that evaluating such pointcuts is 10-15 times slower than evaluating
* normal pointcuts, but they are useful in some cases.
*
@@ -37,6 +44,8 @@ import org.springframework.util.ObjectUtils;
* @author Rob Harrop
* @author Juergen Hoeller
* @author Sam Brannen
* @see NameMatchMethodPointcut
* @see JdkRegexpMethodPointcut
*/
@SuppressWarnings("serial")
public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher, Serializable {
@@ -48,11 +57,10 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher
protected final Class<?> clazz;
/**
* The method against which to match, potentially {@code null}.
* <p>Available for use in subclasses since 6.1.
* An immutable list of distinct method name patterns against which to match.
* @since 6.1
*/
@Nullable
protected final String methodName;
protected final List<String> methodNamePatterns;
private final AtomicInteger evaluationCount = new AtomicInteger();
@@ -62,21 +70,52 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher
* @param clazz the class
*/
public ControlFlowPointcut(Class<?> clazz) {
this(clazz, null);
this(clazz, (String) null);
}
/**
* Construct a new pointcut that matches all calls below the given method
* in the given class.
* <p>If no method name is given, the pointcut matches all control flows
* Construct a new pointcut that matches all calls below a method matching
* the given method name pattern in the given class.
* <p>If no method name pattern is given, the pointcut matches all control flows
* below the given class.
* @param clazz the class
* @param methodName the name of the method (may be {@code null})
* @param methodNamePattern the method name pattern (may be {@code null})
*/
public ControlFlowPointcut(Class<?> clazz, @Nullable String methodName) {
public ControlFlowPointcut(Class<?> clazz, @Nullable String methodNamePattern) {
Assert.notNull(clazz, "Class must not be null");
this.clazz = clazz;
this.methodName = methodName;
this.methodNamePatterns = (methodNamePattern != null ?
Collections.singletonList(methodNamePattern) : Collections.emptyList());
}
/**
* Construct a new pointcut that matches all calls below a method matching
* one of the given method name patterns in the given class.
* <p>If no method name pattern is given, the pointcut matches all control flows
* below the given class.
* @param clazz the class
* @param methodNamePatterns the method name patterns (potentially empty)
* @since 6.1
*/
public ControlFlowPointcut(Class<?> clazz, String... methodNamePatterns) {
this(clazz, Arrays.asList(methodNamePatterns));
}
/**
* Construct a new pointcut that matches all calls below a method matching
* one of the given method name patterns in the given class.
* <p>If no method name pattern is given, the pointcut matches all control flows
* below the given class.
* @param clazz the class
* @param methodNamePatterns the method name patterns (potentially empty)
* @since 6.1
*/
public ControlFlowPointcut(Class<?> clazz, List<String> methodNamePatterns) {
Assert.notNull(clazz, "Class must not be null");
Assert.notNull(methodNamePatterns, "List of method name patterns must not be null");
Assert.noNullElements(methodNamePatterns, "List of method name patterns must not contain null elements");
this.clazz = clazz;
this.methodNamePatterns = methodNamePatterns.stream().distinct().toList();
}
@@ -108,9 +147,16 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher
incrementEvaluationCount();
for (StackTraceElement element : new Throwable().getStackTrace()) {
if (element.getClassName().equals(this.clazz.getName()) &&
(this.methodName == null || element.getMethodName().equals(this.methodName))) {
return true;
if (element.getClassName().equals(this.clazz.getName())) {
if (this.methodNamePatterns.isEmpty()) {
return true;
}
String methodName = element.getMethodName();
for (int i = 0; i < this.methodNamePatterns.size(); i++) {
if (isMatch(methodName, i)) {
return true;
}
}
}
}
return false;
@@ -134,6 +180,47 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher
this.evaluationCount.incrementAndGet();
}
/**
* Determine if the given method name matches the method name pattern at the
* specified index.
* <p>This method is invoked by {@link #matches(Method, Class, Object...)}.
* <p>The default implementation retrieves the method name pattern from
* {@link #methodNamePatterns} and delegates to {@link #isMatch(String, String)}.
* <p>Can be overridden in subclasses &mdash; for example, to support
* regular expressions.
* @param methodName the method name to check
* @param patternIndex the index of the method name pattern
* @return {@code true} if the method name matches the pattern at the specified
* index
* @since 6.1
* @see #methodNamePatterns
* @see #isMatch(String, String)
* @see #matches(Method, Class, Object...)
*/
protected boolean isMatch(String methodName, int patternIndex) {
String methodNamePattern = this.methodNamePatterns.get(patternIndex);
return isMatch(methodName, methodNamePattern);
}
/**
* Determine if the given method name matches the method name pattern.
* <p>This method is invoked by {@link #isMatch(String, int)}.
* <p>The default implementation checks for direct equality as well as
* {@code xxx*}, {@code *xxx}, {@code *xxx*}, and {@code xxx*yyy} matches.
* <p>Can be overridden in subclasses &mdash; for example, to support a
* different style of simple pattern matching.
* @param methodName the method name to check
* @param methodNamePattern the method name pattern
* @return {@code true} if the method name matches the pattern
* @since 6.1
* @see #isMatch(String, int)
* @see PatternMatchUtils#simpleMatch(String, String)
*/
protected boolean isMatch(String methodName, String methodNamePattern) {
return (methodName.equals(methodNamePattern) ||
PatternMatchUtils.simpleMatch(methodNamePattern, methodName));
}
@Override
public ClassFilter getClassFilter() {
@@ -149,22 +236,19 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof ControlFlowPointcut that &&
this.clazz.equals(that.clazz)) &&
ObjectUtils.nullSafeEquals(this.methodName, that.methodName));
this.clazz.equals(that.clazz)) && this.methodNamePatterns.equals(that.methodNamePatterns));
}
@Override
public int hashCode() {
int code = this.clazz.hashCode();
if (this.methodName != null) {
code = 37 * code + this.methodName.hashCode();
}
code = 37 * code + this.methodNamePatterns.hashCode();
return code;
}
@Override
public String toString() {
return getClass().getName() + ": class = " + this.clazz.getName() + "; methodName = " + this.methodName;
return getClass().getName() + ": class = " + this.clazz.getName() + "; methodNamePatterns = " + this.methodNamePatterns;
}
}
@@ -1,5 +1,6 @@
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.aop.scope.ScopedProxyBeanRegistrationAotProcessor
org.springframework.aop.scope.ScopedProxyBeanRegistrationAotProcessor,\
org.springframework.aop.aspectj.annotation.AspectJAdvisorBeanRegistrationAotProcessor
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor= \
org.springframework.aop.aspectj.annotation.AspectJBeanFactoryInitializationAotProcessor
@@ -0,0 +1,83 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.aspectj.annotation;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection;
/**
* Tests for {@link AspectJAdvisorBeanRegistrationAotProcessor}.
*
* @author Sebastien Deleuze
*/
class AspectJAdvisorBeanRegistrationAotProcessorTests {
private final GenerationContext generationContext = new TestGenerationContext();
private final RuntimeHints runtimeHints = this.generationContext.getRuntimeHints();
@Test
void shouldProcessesAspectJClass() {
process(AspectJClass.class);
assertThat(reflection().onType(AspectJClass.class).withMemberCategory(MemberCategory.DECLARED_FIELDS))
.accepts(this.runtimeHints);
}
@Test
void shouldSkipRegularClass() {
process(RegularClass.class);
assertThat(this.runtimeHints.reflection().typeHints()).isEmpty();
}
void process(Class<?> beanClass) {
BeanRegistrationAotContribution contribution = createContribution(beanClass);
if (contribution != null) {
contribution.applyTo(this.generationContext, mock());
}
}
@Nullable
private static BeanRegistrationAotContribution createContribution(Class<?> beanClass) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition(beanClass.getName(), new RootBeanDefinition(beanClass));
return new AspectJAdvisorBeanRegistrationAotProcessor()
.processAheadOfTime(RegisteredBean.of(beanFactory, beanClass.getName()));
}
static class AspectJClass {
private static java.lang.Throwable ajc$initFailureCause;
}
static class RegularClass {
private static java.lang.Throwable initFailureCause;
}
}
@@ -17,6 +17,8 @@
package org.springframework.aop.support;
import java.lang.reflect.Method;
import java.util.List;
import java.util.regex.Pattern;
import org.junit.jupiter.api.Test;
@@ -38,110 +40,88 @@ import static org.assertj.core.api.Assertions.assertThat;
class ControlFlowPointcutTests {
@Test
void matches() {
TestBean target = new TestBean();
target.setAge(27);
void matchesExactMethodName() {
MyComponent component = new MyComponent();
TestBean target = new TestBean("Jane", 27);
ControlFlowPointcut cflow = pointcut("getAge");
NopInterceptor nop = new NopInterceptor();
ControlFlowPointcut cflow = new ControlFlowPointcut(One.class, "getAge");
ProxyFactory pf = new ProxyFactory(target);
ITestBean proxied = (ITestBean) pf.getProxy();
pf.addAdvisor(new DefaultPointcutAdvisor(cflow, nop));
ITestBean proxy = (ITestBean) pf.getProxy();
// Not advised, not under One
assertThat(proxied.getAge()).isEqualTo(target.getAge());
assertThat(nop.getCount()).isEqualTo(0);
// Will not be advised: not under MyComponent
assertThat(proxy.getAge()).isEqualTo(target.getAge());
assertThat(cflow.getEvaluations()).isEqualTo(1);
assertThat(nop.getCount()).isEqualTo(0);
// Will be advised
assertThat(new One().getAge(proxied)).isEqualTo(target.getAge());
assertThat(nop.getCount()).isEqualTo(1);
// Will be advised due to "getAge" pattern: the proxy is invoked under MyComponent#getAge
assertThat(component.getAge(proxy)).isEqualTo(target.getAge());
assertThat(cflow.getEvaluations()).isEqualTo(2);
// Won't be advised
assertThat(new One().nomatch(proxied)).isEqualTo(target.getAge());
assertThat(nop.getCount()).isEqualTo(1);
// Will not be advised: the proxy is invoked under MyComponent, but there is no match for "nomatch"
assertThat(component.nomatch(proxy)).isEqualTo(target.getAge());
assertThat(cflow.getEvaluations()).isEqualTo(3);
assertThat(nop.getCount()).isEqualTo(1);
}
@Test
void matchesMethodNamePatterns() {
ControlFlowPointcut cflow = pointcut("set", "getAge");
assertMatchesSetAndGetAge(cflow);
cflow = pointcut("foo", "get*", "bar", "*se*", "baz");
assertMatchesSetAndGetAge(cflow);
}
@Test
void regExControlFlowPointcut() {
ControlFlowPointcut cflow = new RegExControlFlowPointcut(MyComponent.class, "(set.*?|getAge)");
assertMatchesSetAndGetAge(cflow);
cflow = new RegExControlFlowPointcut(MyComponent.class, "set", "^getAge$");
assertMatchesSetAndGetAge(cflow);
}
@Test
void controlFlowPointcutIsExtensible() {
@SuppressWarnings("serial")
class CustomControlFlowPointcut extends ControlFlowPointcut {
CustomControlFlowPointcut(Class<?> clazz, String methodName) {
super(clazz, methodName);
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
super.incrementEvaluationCount();
return super.matches(method, targetClass, args);
}
Class<?> trackedClass() {
return super.clazz;
}
String trackedMethod() {
return super.methodName;
}
}
CustomControlFlowPointcut cflow = new CustomControlFlowPointcut(One.class, "getAge");
assertThat(cflow.trackedClass()).isEqualTo(One.class);
assertThat(cflow.trackedMethod()).isEqualTo("getAge");
TestBean target = new TestBean("Jane", 27);
ProxyFactory pf = new ProxyFactory(target);
NopInterceptor nop = new NopInterceptor();
pf.addAdvisor(new DefaultPointcutAdvisor(cflow, nop));
ITestBean proxy = (ITestBean) pf.getProxy();
// Not advised: the proxy is not invoked under One#getAge
assertThat(proxy.getAge()).isEqualTo(target.getAge());
assertThat(nop.getCount()).isEqualTo(0);
assertThat(cflow.getEvaluations()).isEqualTo(2); // intentional double increment
// Will be advised: the proxy is invoked under One#getAge
assertThat(new One().getAge(proxy)).isEqualTo(target.getAge());
assertThat(nop.getCount()).isEqualTo(1);
assertThat(cflow.getEvaluations()).isEqualTo(4); // intentional double increment
// Won't be advised: the proxy is not invoked under One#getAge
assertThat(new One().nomatch(proxy)).isEqualTo(target.getAge());
assertThat(nop.getCount()).isEqualTo(1);
assertThat(cflow.getEvaluations()).isEqualTo(6); // intentional double increment
CustomControlFlowPointcut cflow = new CustomControlFlowPointcut(MyComponent.class, "set*", "getAge", "set*", "set*");
assertMatchesSetAndGetAge(cflow, 2);
assertThat(cflow.trackedClass()).isEqualTo(MyComponent.class);
assertThat(cflow.trackedMethodNamePatterns()).containsExactly("set*", "getAge");
}
/**
* Check that we can use a cflow pointcut only in conjunction with
* Check that we can use a cflow pointcut in conjunction with
* a static pointcut: e.g. all setter methods that are invoked under
* a particular class. This greatly reduces the number of calls
* to the cflow pointcut, meaning that it's not so prohibitively
* expensive.
*/
@Test
void selectiveApplication() {
TestBean target = new TestBean();
target.setAge(27);
void controlFlowPointcutCanBeCombinedWithStaticPointcut() {
MyComponent component = new MyComponent();
TestBean target = new TestBean("Jane", 27);
ControlFlowPointcut cflow = pointcut();
Pointcut settersUnderMyComponent = Pointcuts.intersection(Pointcuts.SETTERS, cflow);
NopInterceptor nop = new NopInterceptor();
ControlFlowPointcut cflow = new ControlFlowPointcut(One.class);
Pointcut settersUnderOne = Pointcuts.intersection(Pointcuts.SETTERS, cflow);
ProxyFactory pf = new ProxyFactory(target);
ITestBean proxied = (ITestBean) pf.getProxy();
pf.addAdvisor(new DefaultPointcutAdvisor(settersUnderOne, nop));
pf.addAdvisor(new DefaultPointcutAdvisor(settersUnderMyComponent, nop));
ITestBean proxy = (ITestBean) pf.getProxy();
// Not advised, not under One
// Will not be advised: not under MyComponent
target.setAge(16);
assertThat(cflow.getEvaluations()).isEqualTo(0);
assertThat(nop.getCount()).isEqualTo(0);
// Not advised; under One but not a setter
assertThat(new One().getAge(proxied)).isEqualTo(16);
// Will not be advised: under MyComponent but not a setter
assertThat(component.getAge(proxy)).isEqualTo(16);
assertThat(cflow.getEvaluations()).isEqualTo(0);
assertThat(nop.getCount()).isEqualTo(0);
// Won't be advised
new One().set(proxied);
// Will be advised due to Pointcuts.SETTERS: the proxy is invoked under MyComponent#set
component.set(proxy);
assertThat(proxy.getAge()).isEqualTo(5);
assertThat(nop.getCount()).isEqualTo(1);
// We saved most evaluations
@@ -149,34 +129,149 @@ class ControlFlowPointcutTests {
}
@Test
void equalsAndHashCode() throws Exception {
assertThat(new ControlFlowPointcut(One.class)).isEqualTo(new ControlFlowPointcut(One.class));
assertThat(new ControlFlowPointcut(One.class, "getAge")).isEqualTo(new ControlFlowPointcut(One.class, "getAge"));
assertThat(new ControlFlowPointcut(One.class, "getAge")).isNotEqualTo(new ControlFlowPointcut(One.class));
void equalsAndHashCode() {
assertThat(pointcut()).isEqualTo(pointcut());
assertThat(pointcut()).hasSameHashCodeAs(pointcut());
assertThat(new ControlFlowPointcut(One.class)).hasSameHashCodeAs(new ControlFlowPointcut(One.class));
assertThat(new ControlFlowPointcut(One.class, "getAge")).hasSameHashCodeAs(new ControlFlowPointcut(One.class, "getAge"));
assertThat(new ControlFlowPointcut(One.class, "getAge")).doesNotHaveSameHashCodeAs(new ControlFlowPointcut(One.class));
assertThat(pointcut("getAge")).isEqualTo(pointcut("getAge"));
assertThat(pointcut("getAge")).hasSameHashCodeAs(pointcut("getAge"));
assertThat(pointcut("getAge")).isNotEqualTo(pointcut());
assertThat(pointcut("getAge")).doesNotHaveSameHashCodeAs(pointcut());
assertThat(pointcut("get*", "set*")).isEqualTo(pointcut("get*", "set*"));
assertThat(pointcut("get*", "set*")).isEqualTo(pointcut("get*", "set*", "set*", "get*"));
assertThat(pointcut("get*", "set*")).hasSameHashCodeAs(pointcut("get*", "get*", "set*"));
assertThat(pointcut("get*", "set*")).isNotEqualTo(pointcut("set*", "get*"));
assertThat(pointcut("get*", "set*")).doesNotHaveSameHashCodeAs(pointcut("set*", "get*"));
assertThat(pointcut("get*", "set*")).isEqualTo(pointcut(List.of("get*", "set*")));
assertThat(pointcut("get*", "set*")).isEqualTo(pointcut(List.of("get*", "set*", "set*", "get*")));
assertThat(pointcut("get*", "set*")).hasSameHashCodeAs(pointcut(List.of("get*", "get*", "set*")));
}
@Test
void testToString() {
assertThat(new ControlFlowPointcut(One.class)).asString()
.isEqualTo(ControlFlowPointcut.class.getName() + ": class = " + One.class.getName() + "; methodName = null");
assertThat(new ControlFlowPointcut(One.class, "getAge")).asString()
.isEqualTo(ControlFlowPointcut.class.getName() + ": class = " + One.class.getName() + "; methodName = getAge");
String pointcutType = ControlFlowPointcut.class.getName();
String componentType = MyComponent.class.getName();
assertThat(pointcut()).asString()
.startsWith(pointcutType)
.contains(componentType)
.endsWith("[]");
assertThat(pointcut("getAge")).asString()
.startsWith(pointcutType)
.contains(componentType)
.endsWith("[getAge]");
assertThat(pointcut("get*", "set*", "get*")).asString()
.startsWith(pointcutType)
.contains(componentType)
.endsWith("[get*, set*]");
}
private static class One {
int getAge(ITestBean proxied) {
return proxied.getAge();
private static ControlFlowPointcut pointcut() {
return new ControlFlowPointcut(MyComponent.class);
}
private static ControlFlowPointcut pointcut(String methodNamePattern) {
return new ControlFlowPointcut(MyComponent.class, methodNamePattern);
}
private static ControlFlowPointcut pointcut(String... methodNamePatterns) {
return new ControlFlowPointcut(MyComponent.class, methodNamePatterns);
}
private static ControlFlowPointcut pointcut(List<String> methodNamePatterns) {
return new ControlFlowPointcut(MyComponent.class, methodNamePatterns);
}
private static void assertMatchesSetAndGetAge(ControlFlowPointcut cflow) {
assertMatchesSetAndGetAge(cflow, 1);
}
private static void assertMatchesSetAndGetAge(ControlFlowPointcut cflow, int evaluationFactor) {
MyComponent component = new MyComponent();
TestBean target = new TestBean("Jane", 27);
NopInterceptor nop = new NopInterceptor();
ProxyFactory pf = new ProxyFactory(target);
pf.addAdvisor(new DefaultPointcutAdvisor(cflow, nop));
ITestBean proxy = (ITestBean) pf.getProxy();
// Will not be advised: not under MyComponent
assertThat(proxy.getAge()).isEqualTo(target.getAge());
assertThat(cflow.getEvaluations()).isEqualTo(1 * evaluationFactor);
assertThat(nop.getCount()).isEqualTo(0);
// Will be advised: the proxy is invoked under MyComponent#getAge
assertThat(component.getAge(proxy)).isEqualTo(target.getAge());
assertThat(cflow.getEvaluations()).isEqualTo(2 * evaluationFactor);
assertThat(nop.getCount()).isEqualTo(1);
// Will be advised: the proxy is invoked under MyComponent#set
component.set(proxy);
assertThat(cflow.getEvaluations()).isEqualTo(3 * evaluationFactor);
assertThat(proxy.getAge()).isEqualTo(5);
assertThat(cflow.getEvaluations()).isEqualTo(4 * evaluationFactor);
assertThat(nop.getCount()).isEqualTo(2);
// Will not be advised: the proxy is invoked under MyComponent, but there is no match for "nomatch"
assertThat(component.nomatch(proxy)).isEqualTo(target.getAge());
assertThat(nop.getCount()).isEqualTo(2);
assertThat(cflow.getEvaluations()).isEqualTo(5 * evaluationFactor);
}
private static class MyComponent {
int getAge(ITestBean proxy) {
return proxy.getAge();
}
int nomatch(ITestBean proxied) {
return proxied.getAge();
int nomatch(ITestBean proxy) {
return proxy.getAge();
}
void set(ITestBean proxied) {
proxied.setAge(5);
void set(ITestBean proxy) {
proxy.setAge(5);
}
}
@SuppressWarnings("serial")
private static class CustomControlFlowPointcut extends ControlFlowPointcut {
CustomControlFlowPointcut(Class<?> clazz, String... methodNamePatterns) {
super(clazz, methodNamePatterns);
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
super.incrementEvaluationCount();
return super.matches(method, targetClass, args);
}
Class<?> trackedClass() {
return super.clazz;
}
List<String> trackedMethodNamePatterns() {
return super.methodNamePatterns;
}
}
@SuppressWarnings("serial")
private static class RegExControlFlowPointcut extends ControlFlowPointcut {
private final List<Pattern> compiledPatterns;
RegExControlFlowPointcut(Class<?> clazz, String... methodNamePatterns) {
super(clazz, methodNamePatterns);
this.compiledPatterns = super.methodNamePatterns.stream().map(Pattern::compile).toList();
}
@Override
protected boolean isMatch(String methodName, int patternIndex) {
return this.compiledPatterns.get(patternIndex).matcher(methodName).matches();
}
}
@@ -30,34 +30,35 @@ import org.springframework.core.testfixture.io.SerializationTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link NameMatchMethodPointcut}.
*
* @author Rod Johnson
* @author Chris Beams
* @author Sam Brannen
*/
public class NameMatchMethodPointcutTests {
class NameMatchMethodPointcutTests {
protected NameMatchMethodPointcut pc;
private final NameMatchMethodPointcut pc = new NameMatchMethodPointcut();
protected Person proxied;
private final SerializableNopInterceptor nop = new SerializableNopInterceptor();
protected SerializableNopInterceptor nop;
private Person personProxy;
/**
* Create an empty pointcut, populating instance variables.
*/
@BeforeEach
public void setup() {
void setup() {
ProxyFactory pf = new ProxyFactory(new SerializablePerson());
nop = new SerializableNopInterceptor();
pc = new NameMatchMethodPointcut();
pf.addAdvisor(new DefaultPointcutAdvisor(pc, nop));
proxied = (Person) pf.getProxy();
personProxy = (Person) pf.getProxy();
}
@Test
public void testMatchingOnly() {
// Can't do exact matching through isMatch
void isMatch() {
assertThat(pc.isMatch("echo", "echo")).isTrue();
assertThat(pc.isMatch("echo", "ech*")).isTrue();
assertThat(pc.isMatch("setName", "setN*")).isTrue();
assertThat(pc.isMatch("setName", "set*")).isTrue();
@@ -67,73 +68,87 @@ public class NameMatchMethodPointcutTests {
}
@Test
public void testEmpty() throws Throwable {
void noMappedMethodNamePatterns() throws Throwable {
assertThat(nop.getCount()).isEqualTo(0);
proxied.getName();
proxied.setName("");
proxied.echo(null);
personProxy.getName();
personProxy.setName("");
personProxy.echo(null);
assertThat(nop.getCount()).isEqualTo(0);
}
@Test
public void testMatchOneMethod() throws Throwable {
void methodNamePatternsMappedIndividually() throws Throwable {
pc.addMethodName("echo");
pc.addMethodName("set*");
assertThat(nop.getCount()).isEqualTo(0);
proxied.getName();
proxied.getName();
personProxy.getName();
assertThat(nop.getCount()).isEqualTo(0);
proxied.echo(null);
personProxy.getName();
assertThat(nop.getCount()).isEqualTo(0);
personProxy.echo(null);
assertThat(nop.getCount()).isEqualTo(1);
proxied.setName("");
personProxy.setName("");
assertThat(nop.getCount()).isEqualTo(2);
proxied.setAge(25);
assertThat(proxied.getAge()).isEqualTo(25);
personProxy.setAge(25);
assertThat(nop.getCount()).isEqualTo(3);
assertThat(personProxy.getAge()).isEqualTo(25);
}
@Test
public void testSets() throws Throwable {
void methodNamePatternsMappedAsVarargs() throws Throwable {
pc.setMappedNames("set*", "echo");
assertThat(nop.getCount()).isEqualTo(0);
proxied.getName();
proxied.setName("");
personProxy.getName();
assertThat(nop.getCount()).isEqualTo(0);
personProxy.setName("");
assertThat(nop.getCount()).isEqualTo(1);
proxied.echo(null);
personProxy.echo(null);
assertThat(nop.getCount()).isEqualTo(2);
}
@Test
public void testSerializable() throws Throwable {
testSets();
// Count is now 2
Person p2 = SerializationTestUtils.serializeAndDeserialize(proxied);
void serializable() throws Throwable {
methodNamePatternsMappedAsVarargs();
Person p2 = SerializationTestUtils.serializeAndDeserialize(personProxy);
NopInterceptor nop2 = (NopInterceptor) ((Advised) p2).getAdvisors()[0].getAdvice();
// nop.getCount() should still be 2.
assertThat(nop2.getCount()).isEqualTo(2);
p2.getName();
assertThat(nop2.getCount()).isEqualTo(2);
p2.echo(null);
assertThat(nop2.getCount()).isEqualTo(3);
}
@Test
public void testEqualsAndHashCode() {
void equalsAndHashCode() {
NameMatchMethodPointcut pc1 = new NameMatchMethodPointcut();
NameMatchMethodPointcut pc2 = new NameMatchMethodPointcut();
String foo = "foo";
String mappedNamePattern = "foo";
assertThat(pc2).isEqualTo(pc1);
assertThat(pc2.hashCode()).isEqualTo(pc1.hashCode());
assertThat(pc2).hasSameHashCodeAs(pc1);
pc1.setMappedName(foo);
assertThat(pc1.equals(pc2)).isFalse();
assertThat(pc1.hashCode()).isNotEqualTo(pc2.hashCode());
pc1.setMappedName(mappedNamePattern);
assertThat(pc1).isNotEqualTo(pc2);
assertThat(pc1).doesNotHaveSameHashCodeAs(pc2);
pc2.setMappedName(foo);
pc2.setMappedName(mappedNamePattern);
assertThat(pc2).isEqualTo(pc1);
assertThat(pc2.hashCode()).isEqualTo(pc1.hashCode());
assertThat(pc2).hasSameHashCodeAs(pc1);
}
}
@@ -174,7 +174,9 @@ class BeanDefinitionPropertiesCodeGenerator {
Map<Integer, ValueHolder> indexedValues = constructorValues.getIndexedArgumentValues();
if (!indexedValues.isEmpty()) {
indexedValues.forEach((index, valueHolder) -> {
CodeBlock valueCode = generateValue(valueHolder.getName(), valueHolder.getValue());
Object value = valueHolder.getValue();
CodeBlock valueCode = castIfNecessary(value == null, Object.class,
generateValue(valueHolder.getName(), value));
code.addStatement(
"$L.getConstructorArgumentValues().addIndexedArgumentValue($L, $L)",
BEAN_DEFINITION_VARIABLE, index, valueCode);
@@ -346,6 +348,20 @@ class BeanDefinitionPropertiesCodeGenerator {
}
}
/**
* Cast the specified {@code valueCode} to the specified {@code castType} if
* the {@code castNecessary} is {@code true}. Otherwise return the valueCode
* as is.
* @param castNecessary whether a cast is necessary
* @param castType the type to cast to
* @param valueCode the code for the value
* @return the existing value or a form of {@code (CastType) valueCode} if a
* cast is necessary
*/
private CodeBlock castIfNecessary(boolean castNecessary, Class<?> castType, CodeBlock valueCode) {
return (castNecessary ? CodeBlock.of("($T) $L", castType, valueCode) : valueCode);
}
static class PropertyNamesStack {
private static final ThreadLocal<ArrayDeque<String>> threadLocal = ThreadLocal.withInitial(ArrayDeque::new);
@@ -235,6 +235,18 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
});
}
@Test
void constructorArgumentValuesWhenIndexedNullValue() {
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
compile((actual, compiled) -> {
ConstructorArgumentValues argumentValues = actual.getConstructorArgumentValues();
Map<Integer, ValueHolder> values = argumentValues.getIndexedArgumentValues();
assertThat(values.get(0)).satisfies(assertValueHolder(null, null, null));
assertThat(values).hasSize(1);
assertThat(argumentValues.getGenericArgumentValues()).isEmpty();
});
}
@Test
void constructorArgumentValuesWhenGenericValuesWithName() {
this.beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(String.class);
@@ -255,7 +267,9 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
});
}
private Consumer<ValueHolder> assertValueHolder(Object value, @Nullable Class<?> type, @Nullable String name) {
private Consumer<ValueHolder> assertValueHolder(
@Nullable Object value, @Nullable Class<?> type, @Nullable String name) {
return valueHolder -> {
assertThat(valueHolder.getValue()).isEqualTo(value);
assertThat(valueHolder.getType()).isEqualTo((type != null ? type.getName() : null));
@@ -273,7 +287,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
assertThat(actual.getPropertyValues().get("spring")).isEqualTo("framework");
});
assertHasMethodInvokeHints(PropertyValuesBean.class, "setTest", "setSpring");
assertHasDecalredFieldsHint(PropertyValuesBean.class);
assertHasDeclaredFieldsHint(PropertyValuesBean.class);
}
@Test
@@ -286,8 +300,8 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
assertThat(actual.getPropertyValues().get("spring")).isEqualTo("framework");
});
assertHasMethodInvokeHints(PropertyValuesBean.class, "setTest", "setSpring");
assertHasDecalredFieldsHint(ExtendedPropertyValuesBean.class);
assertHasDecalredFieldsHint(PropertyValuesBean.class);
assertHasDeclaredFieldsHint(ExtendedPropertyValuesBean.class);
assertHasDeclaredFieldsHint(PropertyValuesBean.class);
}
@Test
@@ -348,7 +362,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
assertThat(actual.getPropertyValues().get("name")).isEqualTo("World");
});
assertHasMethodInvokeHints(PropertyValuesFactoryBean.class, "setPrefix", "setName" );
assertHasDecalredFieldsHint(PropertyValuesFactoryBean.class);
assertHasDeclaredFieldsHint(PropertyValuesFactoryBean.class);
}
@Test
@@ -502,7 +516,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
.test(this.generationContext.getRuntimeHints()));
}
private void assertHasDecalredFieldsHint(Class<?> beanType) {
private void assertHasDeclaredFieldsHint(Class<?> beanType) {
assertThat(RuntimeHintsPredicates.reflection()
.onType(beanType).withMemberCategory(MemberCategory.DECLARED_FIELDS))
.accepts(this.generationContext.getRuntimeHints());
@@ -193,7 +193,7 @@ public class CaffeineCacheManager implements CacheManager {
* Set the common cache type that this cache manager builds to async.
* This applies to {@link #setCacheNames} as well as on-demand caches.
* <p>Individual cache registrations (such as {@link #registerCustomCache(String, AsyncCache)}
* and {@link #registerCustomCache(String, com.github.benmanes.caffeine.cache.Cache)}
* and {@link #registerCustomCache(String, com.github.benmanes.caffeine.cache.Cache)})
* are not dependent on this setting.
* <p>By default, this cache manager builds regular native Caffeine caches.
* To switch to async caches which can also be used through the synchronous API
@@ -322,7 +322,7 @@ public class CaffeineCacheManager implements CacheManager {
* Build a common {@link CaffeineCache} instance for the specified cache name,
* using the common Caffeine configuration specified on this cache manager.
* <p>Delegates to {@link #adaptCaffeineCache} as the adaptation method to
* Spring's cache abstraction (allowing for centralized decoration etc),
* Spring's cache abstraction (allowing for centralized decoration etc.),
* passing in a freshly built native Caffeine Cache instance.
* @param name the name of the cache
* @return the Spring CaffeineCache adapter (or a decorator thereof)
@@ -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.
@@ -102,7 +102,7 @@ class CachePutOperation extends AbstractJCacheKeyOperation<CachePut> {
result = parameter;
}
else {
throw new IllegalArgumentException("More than one @CacheValue found on " + method + "");
throw new IllegalArgumentException("More than one @CacheValue found on " + method);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -146,7 +146,6 @@ class CacheResultInterceptor extends AbstractKeyCacheInterceptor<CacheResultOper
return new CacheOperationInvoker.ThrowableWrapper(clone);
}
@SuppressWarnings("unchecked")
@Nullable
private static <T extends Throwable> T cloneException(T exception) {
try {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -232,7 +232,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
* Only resolve the default exception cache resolver when an exception needs to be handled.
* <p>A non-JSR-107 setup requires either a {@link CacheManager} or a {@link CacheResolver}.
* If only the latter is specified, it is not possible to extract a default exception
* {@code CacheResolver} from a custom {@code CacheResolver} implementation so we have to
* {@code CacheResolver} from a custom {@code CacheResolver} implementation, so we have to
* fall back on the {@code CacheManager}.
* <p>This gives this weird situation of a perfectly valid configuration that breaks all
* of a sudden because the JCache support is enabled. To avoid this we resolve the default
@@ -511,7 +511,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
"' since its singleton instance has been created too early. The typical cause " +
"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
"return type: Consider declaring such methods as 'static' and/or mark the " +
"return type: Consider declaring such methods as 'static' and/or marking the " +
"containing configuration class as 'proxyBeanMethods=false'.");
}
configBeanDefs.put(beanName, abd);
@@ -73,22 +73,32 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
/**
* Property name for a common context checkpoint: {@value}.
* @since 6.1
* @see #CHECKPOINT_ON_REFRESH_VALUE
* @see #ON_REFRESH_VALUE
* @see org.crac.Core#checkpointRestore()
*/
public static final String CHECKPOINT_PROPERTY_NAME = "spring.context.checkpoint";
/**
* Recognized value for the context checkpoint property: {@value}.
* Property name for terminating the JVM when the context reaches a specific phase: {@value}.
* @since 6.1
* @see #ON_REFRESH_VALUE
*/
public static final String EXIT_PROPERTY_NAME = "spring.context.exit";
/**
* Recognized value for the context checkpoint and exit properties: {@value}.
* @since 6.1
* @see #CHECKPOINT_PROPERTY_NAME
* @see org.crac.Core#checkpointRestore()
* @see #EXIT_PROPERTY_NAME
*/
public static final String CHECKPOINT_ON_REFRESH_VALUE = "onRefresh";
public static final String ON_REFRESH_VALUE = "onRefresh";
private static final boolean checkpointOnRefresh =
CHECKPOINT_ON_REFRESH_VALUE.equalsIgnoreCase(SpringProperties.getProperty(CHECKPOINT_PROPERTY_NAME));
ON_REFRESH_VALUE.equalsIgnoreCase(SpringProperties.getProperty(CHECKPOINT_PROPERTY_NAME));
private static final boolean exitOnRefresh =
ON_REFRESH_VALUE.equalsIgnoreCase(SpringProperties.getProperty(EXIT_PROPERTY_NAME));
private final Log logger = LogFactory.getLog(getClass());
@@ -182,6 +192,9 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
if (checkpointOnRefresh) {
new CracDelegate().checkpointRestore();
}
if (exitOnRefresh) {
Runtime.getRuntime().halt(0);
}
this.stoppedBeans = null;
try {
@@ -86,6 +86,8 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
@Nullable
private ExecutorLifecycleDelegate lifecycleDelegate;
private volatile boolean lateShutdown;
/**
* Set the ThreadFactory to use for the ExecutorService's thread pool.
@@ -127,8 +129,9 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
* <p>Default is {@code false} as of 6.1, triggering an early soft shutdown of
* the executor and therefore rejecting any further task submissions. Switch this
* to {@code true} in order to let other components submit tasks even during their
* own destruction callbacks, at the expense of a longer shutdown phase.
* This will usually go along with
* own stop and destruction callbacks, at the expense of a longer shutdown phase.
* The executor will not go through a coordinated lifecycle stop phase then
* but rather only stop tasks on its own shutdown. This usually goes along with
* {@link #setWaitForTasksToCompleteOnShutdown "waitForTasksToCompleteOnShutdown"}.
* <p>This flag will only have effect when the executor is running in a Spring
* application context and able to receive the {@link ContextClosedEvent}.
@@ -144,9 +147,13 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
/**
* Set whether to wait for scheduled tasks to complete on shutdown,
* not interrupting running tasks and executing all tasks in the queue.
* <p>Default is {@code false}, shutting down immediately through interrupting
* ongoing tasks and clearing the queue. Switch this flag to {@code true} if
* you prefer fully completed tasks at the expense of a longer shutdown phase.
* <p>Default is {@code false}, with a coordinated lifecycle stop first (unless
* {@link #setAcceptTasksAfterContextClose "acceptTasksAfterContextClose"}
* has been set) and then an immediate shutdown through interrupting ongoing
* tasks and clearing the queue. Switch this flag to {@code true} if you
* prefer fully completed tasks at the expense of a longer shutdown phase.
* The executor will not go through a coordinated lifecycle stop phase then
* but rather only stop and wait for task completion on its own shutdown.
* <p>Note that Spring's container shutdown continues while ongoing tasks
* are being completed. If you want this executor to block and wait for the
* termination of tasks before the rest of the container continues to shut
@@ -374,7 +381,7 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
*/
@Override
public void stop() {
if (this.lifecycleDelegate != null) {
if (this.lifecycleDelegate != null && !this.lateShutdown) {
this.lifecycleDelegate.stop();
}
}
@@ -386,9 +393,12 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
*/
@Override
public void stop(Runnable callback) {
if (this.lifecycleDelegate != null) {
if (this.lifecycleDelegate != null && !this.lateShutdown) {
this.lifecycleDelegate.stop(callback);
}
else {
callback.run();
}
}
/**
@@ -439,10 +449,16 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
*/
@Override
public void onApplicationEvent(ContextClosedEvent event) {
if (event.getApplicationContext() == this.applicationContext && !this.acceptTasksAfterContextClose) {
// Early shutdown signal: accept no further tasks, let existing tasks complete
// before hitting the actual destruction step in the shutdown() method above.
initiateShutdown();
if (event.getApplicationContext() == this.applicationContext) {
if (this.acceptTasksAfterContextClose || this.waitForTasksToCompleteOnShutdown) {
// Late shutdown without early stop lifecycle.
this.lateShutdown = true;
}
else {
// Early shutdown signal: accept no further tasks, let existing tasks complete
// before hitting the actual destruction step in the shutdown() method above.
initiateShutdown();
}
}
}
@@ -97,7 +97,7 @@ final class ExecutorLifecycleDelegate implements SmartLifecycle {
@Override
public boolean isRunning() {
return (!this.executor.isShutdown() & !this.paused);
return (!this.paused && !this.executor.isTerminated());
}
void beforeExecute(Thread thread) {
@@ -301,8 +301,8 @@ public class MethodValidationAdapter implements MethodValidator {
Function<Integer, MethodParameter> parameterFunction,
Function<Integer, Object> argumentFunction) {
Map<MethodParameter, ValueResultBuilder> parameterViolations = new LinkedHashMap<>();
Map<Path.Node, BeanResultBuilder> cascadedViolations = new LinkedHashMap<>();
Map<MethodParameter, ParamResultBuilder> paramViolations = new LinkedHashMap<>();
Map<BeanResultKey, BeanResultBuilder> beanViolations = new LinkedHashMap<>();
for (ConstraintViolation<Object> violation : violations) {
Iterator<Path.Node> itr = violation.getPropertyPath().iterator();
@@ -321,27 +321,29 @@ public class MethodValidationAdapter implements MethodValidator {
continue;
}
Object argument = argumentFunction.apply(parameter.getParameterIndex());
Object arg = argumentFunction.apply(parameter.getParameterIndex());
if (!itr.hasNext()) {
parameterViolations
.computeIfAbsent(parameter, p -> new ValueResultBuilder(target, parameter, argument))
paramViolations
.computeIfAbsent(parameter, p -> new ParamResultBuilder(target, parameter, arg))
.addViolation(violation);
}
else {
cascadedViolations
.computeIfAbsent(node, n -> new BeanResultBuilder(parameter, argument, itr.next()))
Object leafBean = violation.getLeafBean();
BeanResultKey key = new BeanResultKey(node, leafBean);
beanViolations
.computeIfAbsent(key, k -> new BeanResultBuilder(parameter, arg, itr.next(), leafBean))
.addViolation(violation);
}
break;
}
}
List<ParameterValidationResult> validatonResultList = new ArrayList<>();
parameterViolations.forEach((parameter, builder) -> validatonResultList.add(builder.build()));
cascadedViolations.forEach((node, builder) -> validatonResultList.add(builder.build()));
validatonResultList.sort(resultComparator);
List<ParameterValidationResult> resultList = new ArrayList<>();
paramViolations.forEach((param, builder) -> resultList.add(builder.build()));
beanViolations.forEach((key, builder) -> resultList.add(builder.build()));
resultList.sort(resultComparator);
return MethodValidationResult.create(target, method, validatonResultList);
return MethodValidationResult.create(target, method, resultList);
}
private MethodParameter initMethodParameter(Method method, int index) {
@@ -394,7 +396,7 @@ public class MethodValidationAdapter implements MethodValidator {
* Builds a validation result for a value method parameter with constraints
* declared directly on it.
*/
private final class ValueResultBuilder {
private final class ParamResultBuilder {
private final Object target;
@@ -405,7 +407,7 @@ public class MethodValidationAdapter implements MethodValidator {
private final List<MessageSourceResolvable> resolvableErrors = new ArrayList<>();
public ValueResultBuilder(Object target, MethodParameter parameter, @Nullable Object argument) {
public ParamResultBuilder(Object target, MethodParameter parameter, @Nullable Object argument) {
this.target = target;
this.parameter = parameter;
this.argument = argument;
@@ -431,7 +433,7 @@ public class MethodValidationAdapter implements MethodValidator {
private final MethodParameter parameter;
@Nullable
private final Object argument;
private final Object bean;
@Nullable
private final Object container;
@@ -446,25 +448,13 @@ public class MethodValidationAdapter implements MethodValidator {
private final Set<ConstraintViolation<Object>> violations = new LinkedHashSet<>();
public BeanResultBuilder(MethodParameter parameter, @Nullable Object argument, Path.Node node) {
this.parameter = parameter;
public BeanResultBuilder(MethodParameter param, @Nullable Object arg, Path.Node node, @Nullable Object leafBean) {
this.parameter = param;
this.bean = leafBean;
this.container = (arg != null && !arg.equals(leafBean) ? arg : null);
this.containerIndex = node.getIndex();
this.containerKey = node.getKey();
if (argument instanceof List<?> list && this.containerIndex != null) {
this.container = list;
argument = list.get(this.containerIndex);
}
else if (argument instanceof Map<?, ?> map && this.containerKey != null) {
this.container = map;
argument = map.get(this.containerKey);
}
else {
this.container = null;
}
this.argument = argument;
this.errors = createBindingResult(parameter, argument);
this.errors = createBindingResult(param, leafBean);
}
public void addViolation(ConstraintViolation<Object> violation) {
@@ -474,12 +464,28 @@ public class MethodValidationAdapter implements MethodValidator {
public ParameterErrors build() {
validatorAdapter.get().processConstraintViolations(this.violations, this.errors);
return new ParameterErrors(
this.parameter, this.argument, this.errors, this.container,
this.parameter, this.bean, this.errors, this.container,
this.containerIndex, this.containerKey);
}
}
/**
* Unique key for cascaded violations associated with a bean.
* <p>The bean may be an element within a container such as a List, Set, array,
* Map, Optional, and others. In that case the {@link Path.Node} represents
* the container element with its index or key, if applicable, while the
* {@link ConstraintViolation#getLeafBean() leafBean} is the actual
* element instance. The pair should be unique. For example in a Set, the
* node is the same but element instances are unique. In a List or Map the
* node is further qualified by an index or key while element instances
* may be the same.
* @param node the path to the bean associated with the violation
* @param leafBean the bean instance
*/
record BeanResultKey(Path.Node node, Object leafBean) { }
/**
* Default algorithm to select an object name, as described in
* {@link #setObjectNameResolver(ObjectNameResolver)}.
@@ -240,7 +240,7 @@ public class MethodValidationInterceptor implements MethodInterceptor {
ReactiveAdapterRegistry.getSharedInstance();
public static Object[] insertAsyncValidation(
static Object[] insertAsyncValidation(
Supplier<SpringValidatorAdapter> validatorAdapterSupplier, boolean adaptViolations,
Object target, Method method, Object[] arguments) {
@@ -32,11 +32,12 @@ import org.springframework.validation.ObjectError;
* {@link Errors#getAllErrors()}, but this subclass provides access to the same
* as {@link FieldError}s.
*
* <p>When the method parameter is a {@link List} or {@link java.util.Map},
* a separate {@link ParameterErrors} is created for each list or map value for
* which there are validation errors. In such cases, the {@link #getContainer()}
* method returns the list or map, while {@link #getContainerIndex()}
* and {@link #getContainerKey()} return the value index or key.
* <p>When the method parameter is a container with multiple elements such as a
* {@link List}, {@link java.util.Set}, array, {@link java.util.Map}, or others,
* then a separate {@link ParameterErrors} is created for each element that has
* errors. In that case, the {@link #getContainer() container},
* {@link #getContainerIndex() containerIndex}, and {@link #getContainerKey() containerKey}
* provide additional context.
*
* @author Rossen Stoyanchev
* @since 6.1
@@ -71,11 +72,12 @@ public class ParameterErrors extends ParameterValidationResult implements Errors
/**
* When {@code @Valid} is declared on a {@link List} or {@link java.util.Map}
* method parameter, this method returns the list or map that contained the
* validated object {@link #getArgument() argument}, while
* {@link #getContainerIndex()} and {@link #getContainerKey()} returns the
* respective index or key.
* When {@code @Valid} is declared on a container of elements such as
* {@link java.util.Collection}, {@link java.util.Map},
* {@link java.util.Optional}, and others, this method returns the container
* of the validated {@link #getArgument() argument}, while
* {@link #getContainerIndex()} and {@link #getContainerKey()} provide
* information about the index or key if applicable.
*/
@Nullable
public Object getContainer() {
@@ -83,9 +85,9 @@ public class ParameterErrors extends ParameterValidationResult implements Errors
}
/**
* When {@code @Valid} is declared on a {@link List}, this method returns
* the index under which the validated object {@link #getArgument() argument}
* is stored in the list {@link #getContainer() container}.
* When {@code @Valid} is declared on an indexed container of elements such as
* {@link List} or array, this method returns the index of the validated
* {@link #getArgument() argument}.
*/
@Nullable
public Integer getContainerIndex() {
@@ -93,9 +95,9 @@ public class ParameterErrors extends ParameterValidationResult implements Errors
}
/**
* When {@code @Valid} is declared on a {@link java.util.Map}, this method
* returns the key under which the validated object {@link #getArgument()
* argument} is stored in the map {@link #getContainer()}.
* When {@code @Valid} is declared on a container of elements referenced by
* key such as {@link java.util.Map}, this method returns the key of the
* validated {@link #getArgument() argument}.
*/
@Nullable
public Object getContainerKey() {
@@ -82,7 +82,7 @@ class InstantFormatterTests {
@ParameterizedTest
@ArgumentsSource(RandomEpochMillisProvider.class)
void should_parse_into_an_Instant_from_epoch_mili(Instant input) throws ParseException {
void should_parse_into_an_Instant_from_epoch_milli(Instant input) throws ParseException {
Instant expected = input;
Instant actual = instantFormatter.parse(Long.toString(input.toEpochMilli()), null);
@@ -44,7 +44,7 @@ class FormattingConversionServiceRuntimeHintsTests {
}
@Test
void montearyAmountHasHints() {
void monetaryAmountHasHints() {
assertThat(RuntimeHintsPredicates.reflection().onType(javax.money.MonetaryAmount.class)).accepts(this.hints);
}
@@ -344,9 +344,10 @@ public class EnableSchedulingTests {
@Override
public TaskScheduler myTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("explicitScheduler-");
scheduler.setAwaitTerminationMillis(1000);
scheduler.setPoolSize(2);
scheduler.setThreadNamePrefix("explicitScheduler-");
scheduler.setAcceptTasksAfterContextClose(true);
scheduler.setAwaitTerminationMillis(1000);
return scheduler;
}
}
@@ -17,13 +17,16 @@
package org.springframework.validation.beanvalidation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.function.Consumer;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -47,9 +50,9 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class MethodValidationAdapterTests {
private static final Person faustino1234 = new Person("Faustino1234");
private static final Person faustino1234 = new Person("Faustino1234", List.of("Working on Spring"));
private static final Person cayetana6789 = new Person("Cayetana6789");
private static final Person cayetana6789 = new Person("Cayetana6789", List.of(" "));
private final MethodValidationAdapter validationAdapter = new MethodValidationAdapter();
@@ -88,7 +91,13 @@ public class MethodValidationAdapterTests {
codes [Size.guardian.name,Size.name,Size.java.lang.String,Size]; \
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
codes [guardian.name,name]; arguments []; default message [name],10,1]; \
default message [size must be between 1 and 10]"""));
default message [size must be between 1 and 10]""", """
Field error in object 'guardian' on field 'hobbies[0]': rejected value [ ]; \
codes [NotBlank.guardian.hobbies[0],NotBlank.guardian.hobbies,NotBlank.hobbies[0],\
NotBlank.hobbies,NotBlank.java.lang.String,NotBlank]; arguments \
[org.springframework.context.support.DefaultMessageSourceResolvable: codes \
[guardian.hobbies[0],hobbies[0]]; arguments []; default message [hobbies[0]]]; \
default message [must not be blank]"""));
assertValueResult(ex.getValueResults().get(0), 2, 3, List.of("""
org.springframework.context.support.DefaultMessageSourceResolvable: \
@@ -106,7 +115,7 @@ public class MethodValidationAdapterTests {
this.validationAdapter.setObjectNameResolver((param, value) -> "studentToAdd");
testArgs(target, method, new Object[] {faustino1234, new Person("Joe"), 1}, ex -> {
testArgs(target, method, new Object[] {faustino1234, new Person("Joe", List.of()), 1}, ex -> {
assertThat(ex.getAllValidationResults()).hasSize(1);
@@ -178,7 +187,49 @@ public class MethodValidationAdapterTests {
codes [Size.people.name,Size.name,Size.java.lang.String,Size]; \
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
codes [people.name,name]; arguments []; default message [name],10,1]; \
default message [size must be between 1 and 10]"""));
default message [size must be between 1 and 10]""", """
Field error in object 'people' on field 'hobbies[0]': rejected value [ ]; \
codes [NotBlank.people.hobbies[0],NotBlank.people.hobbies,NotBlank.hobbies[0],\
NotBlank.hobbies,NotBlank.java.lang.String,NotBlank]; arguments \
[org.springframework.context.support.DefaultMessageSourceResolvable: codes \
[people.hobbies[0],hobbies[0]]; arguments []; default message [hobbies[0]]]; \
default message [must not be blank]"""));
});
}
@Test
void validateSetArgument() {
MyService target = new MyService();
Method method = getMethod(target, "addPeople");
testArgs(target, method, new Object[] {Set.of(faustino1234, cayetana6789)}, ex -> {
assertThat(ex.getAllValidationResults()).hasSize(2);
int paramIndex = 0;
String objectName = "people";
List<ParameterErrors> results = ex.getBeanResults();
assertThat(results).satisfiesExactlyInAnyOrder(
result -> assertBeanResult(result, paramIndex, objectName, faustino1234, List.of("""
Field error in object 'people' on field 'name': rejected value [Faustino1234]; \
codes [Size.people.name,Size.name,Size.java.lang.String,Size]; \
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
codes [people.name,name]; arguments []; default message [name],10,1]; \
default message [size must be between 1 and 10]""")),
result -> assertBeanResult(result, paramIndex, objectName, cayetana6789, List.of("""
Field error in object 'people' on field 'name': rejected value [Cayetana6789]; \
codes [Size.people.name,Size.name,Size.java.lang.String,Size]; \
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
codes [people.name,name]; arguments []; default message [name],10,1]; \
default message [size must be between 1 and 10]""", """
Field error in object 'people' on field 'hobbies[0]': rejected value [ ]; \
codes [NotBlank.people.hobbies[0],NotBlank.people.hobbies,NotBlank.hobbies[0],\
NotBlank.hobbies,NotBlank.java.lang.String,NotBlank]; arguments \
[org.springframework.context.support.DefaultMessageSourceResolvable: codes \
[people.hobbies[0],hobbies[0]]; arguments []; default message [hobbies[0]]]; \
default message [must not be blank]"""))
);
});
}
@@ -191,7 +242,7 @@ public class MethodValidationAdapterTests {
}
private static void assertBeanResult(
ParameterErrors errors, int parameterIndex, String objectName, Object argument,
ParameterErrors errors, int parameterIndex, String objectName, @Nullable Object argument,
List<String> fieldErrors) {
assertThat(errors.getMethodParameter().getParameterIndex()).isEqualTo(parameterIndex);
@@ -234,14 +285,14 @@ public class MethodValidationAdapterTests {
throw new UnsupportedOperationException();
}
public void addPeople(@Valid List<Person> people) {
public void addPeople(@Valid Collection<Person> people) {
}
}
@SuppressWarnings("unused")
private record Person(@Size(min = 1, max = 10) String name) {
private record Person(@Size(min = 1, max = 10) String name, List<@NotBlank String> hobbies) {
}
}
@@ -36,9 +36,11 @@ import org.springframework.validation.method.ParameterErrors;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for method validation proxy with reactor.
*
* @author Rossen Stoyanchev
*/
public class MethodValidationProxyReactorTests {
class MethodValidationProxyReactorTests {
@Test
void validMonoArgument() {
@@ -53,8 +53,6 @@ public final class TestCompiler {
private final JavaCompiler compiler;
private final Locale locale;
private final SourceFiles sourceFiles;
private final ResourceFiles resourceFiles;
@@ -66,13 +64,12 @@ public final class TestCompiler {
private final List<String> compilerOptions;
private TestCompiler(@Nullable ClassLoader classLoader, JavaCompiler compiler, Locale locale,
SourceFiles sourceFiles, ResourceFiles resourceFiles, ClassFiles classFiles, List<Processor> processors,
List<String> compilerOptions) {
private TestCompiler(@Nullable ClassLoader classLoader, JavaCompiler compiler,
SourceFiles sourceFiles, ResourceFiles resourceFiles, ClassFiles classFiles,
List<Processor> processors, List<String> compilerOptions) {
this.classLoader = classLoader;
this.compiler = compiler;
this.locale = locale;
this.sourceFiles = sourceFiles;
this.resourceFiles = resourceFiles;
this.classFiles = classFiles;
@@ -95,9 +92,8 @@ public final class TestCompiler {
* @return a new {@code TestCompiler} instance
*/
public static TestCompiler forCompiler(JavaCompiler javaCompiler) {
return new TestCompiler(null, javaCompiler, Locale.getDefault(),
SourceFiles.none(), ResourceFiles.none(),
ClassFiles.none(), Collections.emptyList(), Collections.emptyList());
return new TestCompiler(null, javaCompiler, SourceFiles.none(),
ResourceFiles.none(), ClassFiles.none(), Collections.emptyList(), Collections.emptyList());
}
/**
@@ -109,26 +105,13 @@ public final class TestCompiler {
return customizer.apply(this);
}
/**
* Create a new {@code TestCompiler} instance that uses the specified {@link Locale}
* to render compiler messages.
* @param locale the locale to use
* @return a new {@code TestCompiler} instance
* @since 6.1
*/
public TestCompiler withLocale(Locale locale) {
return new TestCompiler(this.classLoader, this.compiler, locale,
this.sourceFiles, this.resourceFiles,
this.classFiles, this.processors, this.compilerOptions);
}
/**
* Create a new {@code TestCompiler} instance with additional source files.
* @param sourceFiles the additional source files
* @return a new {@code TestCompiler} instance
*/
public TestCompiler withSources(SourceFile... sourceFiles) {
return new TestCompiler(this.classLoader, this.compiler, this.locale,
return new TestCompiler(this.classLoader, this.compiler,
this.sourceFiles.and(sourceFiles), this.resourceFiles,
this.classFiles, this.processors, this.compilerOptions);
}
@@ -139,7 +122,7 @@ public final class TestCompiler {
* @return a new {@code TestCompiler} instance
*/
public TestCompiler withSources(Iterable<SourceFile> sourceFiles) {
return new TestCompiler(this.classLoader, this.compiler, this.locale,
return new TestCompiler(this.classLoader, this.compiler,
this.sourceFiles.and(sourceFiles), this.resourceFiles,
this.classFiles, this.processors, this.compilerOptions);
}
@@ -150,7 +133,7 @@ public final class TestCompiler {
* @return a new {@code TestCompiler} instance
*/
public TestCompiler withSources(SourceFiles sourceFiles) {
return new TestCompiler(this.classLoader, this.compiler, this.locale,
return new TestCompiler(this.classLoader, this.compiler,
this.sourceFiles.and(sourceFiles), this.resourceFiles,
this.classFiles, this.processors, this.compilerOptions);
}
@@ -161,9 +144,9 @@ public final class TestCompiler {
* @return a new {@code TestCompiler} instance
*/
public TestCompiler withResources(ResourceFile... resourceFiles) {
return new TestCompiler(this.classLoader, this.compiler, this.locale,
this.sourceFiles, this.resourceFiles.and(resourceFiles),
this.classFiles, this.processors, this.compilerOptions);
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles.and(resourceFiles), this.classFiles, this.processors,
this.compilerOptions);
}
/**
@@ -172,9 +155,9 @@ public final class TestCompiler {
* @return a new {@code TestCompiler} instance
*/
public TestCompiler withResources(Iterable<ResourceFile> resourceFiles) {
return new TestCompiler(this.classLoader, this.compiler, this.locale,
this.sourceFiles, this.resourceFiles.and(resourceFiles),
this.classFiles, this.processors, this.compilerOptions);
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles.and(resourceFiles), this.classFiles, this.processors,
this.compilerOptions);
}
/**
@@ -183,9 +166,9 @@ public final class TestCompiler {
* @return a new {@code TestCompiler} instance
*/
public TestCompiler withResources(ResourceFiles resourceFiles) {
return new TestCompiler(this.classLoader, this.compiler, this.locale,
this.sourceFiles, this.resourceFiles.and(resourceFiles),
this.classFiles, this.processors, this.compilerOptions);
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles.and(resourceFiles), this.classFiles, this.processors,
this.compilerOptions);
}
/**
@@ -194,9 +177,9 @@ public final class TestCompiler {
* @return a new {@code TestCompiler} instance
*/
public TestCompiler withClasses(Iterable<ClassFile> classFiles) {
return new TestCompiler(this.classLoader, this.compiler, this.locale,
this.sourceFiles, this.resourceFiles, this.classFiles.and(classFiles),
this.processors, this.compilerOptions);
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles, this.classFiles.and(classFiles), this.processors,
this.compilerOptions);
}
/**
@@ -207,9 +190,8 @@ public final class TestCompiler {
public TestCompiler withProcessors(Processor... processors) {
List<Processor> mergedProcessors = new ArrayList<>(this.processors);
mergedProcessors.addAll(Arrays.asList(processors));
return new TestCompiler(this.classLoader, this.compiler, this.locale,
this.sourceFiles, this.resourceFiles, this.classFiles, mergedProcessors,
this.compilerOptions);
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles, this.classFiles, mergedProcessors, this.compilerOptions);
}
/**
@@ -220,9 +202,8 @@ public final class TestCompiler {
public TestCompiler withProcessors(Iterable<Processor> processors) {
List<Processor> mergedProcessors = new ArrayList<>(this.processors);
processors.forEach(mergedProcessors::add);
return new TestCompiler(this.classLoader, this.compiler, this.locale,
this.sourceFiles, this.resourceFiles, this.classFiles,
mergedProcessors, this.compilerOptions);
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles, this.classFiles, mergedProcessors, this.compilerOptions);
}
/**
@@ -234,9 +215,8 @@ public final class TestCompiler {
public TestCompiler withCompilerOptions(String... options) {
List<String> mergedCompilerOptions = Stream.concat(this.compilerOptions.stream(),
Arrays.stream(options)).distinct().toList();
return new TestCompiler(this.classLoader, this.compiler, this.locale,
this.sourceFiles, this.resourceFiles, this.classFiles,
this.processors, mergedCompilerOptions);
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
this.resourceFiles, this.classFiles, this.processors, mergedCompilerOptions);
}
/**
@@ -328,7 +308,7 @@ public final class TestCompiler {
DynamicJavaFileManager fileManager = new DynamicJavaFileManager(
standardFileManager, classLoaderToUse, this.classFiles, this.resourceFiles);
if (!this.sourceFiles.isEmpty()) {
Errors errors = new Errors(this.locale);
Errors errors = new Errors();
CompilationTask task = this.compiler.getTask(null, fileManager, errors,
this.compilerOptions, null, compilationUnits);
if (!this.processors.isEmpty()) {
@@ -369,19 +349,13 @@ public final class TestCompiler {
*/
static class Errors implements DiagnosticListener<JavaFileObject> {
private final Locale locale;
private final StringBuilder message = new StringBuilder();
Errors(Locale locale) {
this.locale = locale;
}
@Override
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
this.message.append('\n');
this.message.append(diagnostic.getMessage(this.locale));
this.message.append(diagnostic.getMessage(Locale.getDefault()));
if (diagnostic.getSource() != null) {
this.message.append(' ');
this.message.append(diagnostic.getSource().getName());
@@ -19,7 +19,6 @@ package org.springframework.core.test.tools;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.function.Supplier;
@@ -171,9 +170,9 @@ class TestCompilerTests {
}
""");
assertThatExceptionOfType(CompilationException.class).isThrownBy(
() -> TestCompiler.forSystem().failOnWarning().withLocale(Locale.ENGLISH)
.withSources(SourceFile.of(HELLO_DEPRECATED), main).compile(compiled -> {
})).withMessageContaining("warnings found and -Werror specified");
() -> TestCompiler.forSystem().failOnWarning().withSources(
SourceFile.of(HELLO_DEPRECATED), main).compile(compiled -> {
}));
}
@Test
@@ -62,7 +62,7 @@ public class CompositePropertySourceBenchmark {
CompositePropertySource composite;
@Param({ "2", "5", "10" })
int numberOfPropertySource;
int numberOfPropertySources;
@Param({ "10", "100", "1000" })
int numberOfPropertyNamesPerSource;
@@ -70,7 +70,7 @@ public class CompositePropertySourceBenchmark {
@Setup(Level.Trial)
public void setUp() {
this.composite = new CompositePropertySource("benchmark");
for (int i = 0; i < this.numberOfPropertySource; i++) {
for (int i = 0; i < this.numberOfPropertySources; i++) {
Map<String, Object> map = new HashMap<>(this.numberOfPropertyNamesPerSource);
for (int j = 0; j < this.numberOfPropertyNamesPerSource; j++) {
map.put(ID_GENERATOR.generateId().toString(), VALUE);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -17,7 +17,6 @@
package org.springframework.aot.generate;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@@ -42,7 +41,7 @@ class AppendableConsumerInputStreamSource implements InputStreamSource {
@Override
public InputStream getInputStream() throws IOException {
public InputStream getInputStream() {
return new ByteArrayInputStream(toString().getBytes(StandardCharsets.UTF_8));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -161,7 +161,8 @@ public interface GeneratedFiles {
private static String getClassNamePath(String className) {
Assert.hasLength(className, "'className' must not be empty");
Assert.isTrue(isJavaIdentifier(className), "'className' must be a valid identifier");
Assert.isTrue(isJavaIdentifier(className),
"'className' must be a valid identifier, got '" + className + "'");
return ClassUtils.convertClassNameToResourcePath(className) + ".java";
}
@@ -159,6 +159,10 @@ public class BindingReflectionHintsRegistrar {
for (ResolvableType genericResolvableType : resolvableType.getGenerics()) {
collectReferencedTypes(types, genericResolvableType);
}
Class<?> superClass = clazz.getSuperclass();
if (superClass != null && superClass != Object.class && superClass != Record.class && superClass != Enum.class) {
types.add(superClass);
}
}
}
@@ -187,7 +191,7 @@ public class BindingReflectionHintsRegistrar {
.from(element, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
.stream(JACKSON_ANNOTATION)
.filter(MergedAnnotation::isMetaPresent)
.forEach(action::accept);
.forEach(action);
}
private void registerHintsForClassAttributes(ReflectionHints hints, MergedAnnotation<Annotation> annotation) {
@@ -124,6 +124,6 @@ public enum MemberCategory {
* reflection for inner classes but rather makes sure they are available
* via a call to {@link Class#getDeclaredClasses}.
*/
DECLARED_CLASSES;
DECLARED_CLASSES
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -143,7 +143,7 @@ public final class ResourcePatternHints {
* @param excludes the exclude patterns (see {@link ResourcePatternHint} documentation)
* @return {@code this}, to facilitate method chaining
*/
public Builder excludes(TypeReference reachableType, String... excludes) {
public Builder excludes(@Nullable TypeReference reachableType, String... excludes) {
List<ResourcePatternHint> newExcludes = Arrays.stream(excludes)
.map(include -> new ResourcePatternHint(include, reachableType)).toList();
this.excludes.addAll(newExcludes);
@@ -156,12 +156,10 @@ public class FilePatternResourceHintsRegistrar {
private final List<String> fileExtensions = new ArrayList<>();
private Builder() {
// no-op
}
/**
* Consider the specified classpath locations.
* <p>A location can either be a special {@value ResourceUtils#CLASSPATH_URL_PREFIX}
@@ -254,7 +252,6 @@ public class FilePatternResourceHintsRegistrar {
public void registerHints(ResourceHints hints, @Nullable ClassLoader classLoader) {
build().registerHints(hints, classLoader);
}
}
}
@@ -19,6 +19,7 @@ package org.springframework.aot.hint.support;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.lang.Nullable;
/**
* {@link RuntimeHintsRegistrar} to register hints for {@link org.springframework.core.KotlinDetector}.
@@ -29,7 +30,7 @@ import org.springframework.aot.hint.TypeReference;
class KotlinDetectorRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.reflection().registerType(TypeReference.of("kotlin.Metadata"))
.registerType(TypeReference.of("kotlin.reflect.full.KClasses"));
}
@@ -20,6 +20,7 @@ import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.lang.Nullable;
/**
* {@link RuntimeHintsRegistrar} for {@link PathMatchingResourcePatternResolver}.
@@ -28,7 +29,7 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
class PathMatchingResourcePatternResolverRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.reflection().registerType(TypeReference.of("org.eclipse.core.runtime.FileLocator"));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -47,9 +47,11 @@ class SpringFactoriesLoaderRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = (classLoader != null ? classLoader
: SpringFactoriesLoaderRuntimeHints.class.getClassLoader());
for (String resourceLocation : RESOURCE_LOCATIONS) {
registerHints(hints, classLoader, resourceLocation);
registerHints(hints, classLoaderToUse, resourceLocation);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -60,8 +60,8 @@ class ResourceHintsWriter {
return attributes;
}
private void handleResourceBundles(Map<String, Object> attributes, Stream<ResourceBundleHint> ressourceBundles) {
addIfNotEmpty(attributes, "bundles", ressourceBundles.map(this::toAttributes).toList());
private void handleResourceBundles(Map<String, Object> attributes, Stream<ResourceBundleHint> resourceBundles) {
addIfNotEmpty(attributes, "bundles", resourceBundles.map(this::toAttributes).toList());
}
private Map<String, Object> toAttributes(ResourceBundleHint hint) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -42,7 +42,6 @@ package org.springframework.core;
* @author Sam Brannen
* @since 2.5
* @see org.springframework.beans.factory.config.PropertyOverrideConfigurer
* @see org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
*/
public interface PriorityOrdered extends Ordered {
}
@@ -130,7 +130,7 @@ public final class ReactiveTypeDescriptor {
/**
* Descriptor for a reactive type that can produce 0..N values.
* Descriptor for a reactive type that can produce {@code 0..N} values.
* @param type the reactive type
* @param emptySupplier a supplier of an empty-value instance of the reactive type
*/
@@ -758,7 +758,7 @@ public class ResolvableType implements Serializable {
* Convenience method that will {@link #getGenerics() get} and
* {@link #resolve() resolve} generic parameters.
* @return an array of resolved generic parameters (the resulting array
* will never be {@code null}, but it may contain {@code null} elements})
* will never be {@code null}, but it may contain {@code null} elements)
* @see #getGenerics()
* @see #resolve()
*/
@@ -39,7 +39,6 @@ import org.springframework.lang.Nullable;
* @author Juergen Hoeller
* @since 3.2.7
* @see org.springframework.beans.StandardBeanInfoFactory#IGNORE_BEANINFO_PROPERTY_NAME
* @see org.springframework.context.index.CandidateComponentsIndexLoader#IGNORE_INDEX
* @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME
* @see org.springframework.expression.spel.SpelParserConfiguration#SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME
* @see org.springframework.jdbc.core.StatementCreatorUtils#IGNORE_GETPARAMETERTYPE_PROPERTY_NAME
@@ -103,8 +103,8 @@ final class AnnotationTypeMapping {
private final Set<Method> claimedAliases = new HashSet<>();
AnnotationTypeMapping(@Nullable AnnotationTypeMapping source,
Class<? extends Annotation> annotationType, @Nullable Annotation annotation) {
AnnotationTypeMapping(@Nullable AnnotationTypeMapping source, Class<? extends Annotation> annotationType,
@Nullable Annotation annotation, Set<Class<? extends Annotation>> visitedAnnotationTypes) {
this.source = source;
this.root = (source != null ? source.getRoot() : this);
@@ -124,7 +124,7 @@ final class AnnotationTypeMapping {
processAliases();
addConventionMappings();
addConventionAnnotationValues();
this.synthesizable = computeSynthesizableFlag();
this.synthesizable = computeSynthesizableFlag(visitedAnnotationTypes);
}
@@ -374,7 +374,10 @@ final class AnnotationTypeMapping {
}
@SuppressWarnings("unchecked")
private boolean computeSynthesizableFlag() {
private boolean computeSynthesizableFlag(Set<Class<? extends Annotation>> visitedAnnotationTypes) {
// Track that we have visited the current annotation type.
visitedAnnotationTypes.add(this.annotationType);
// Uses @AliasFor for local aliases?
for (int index : this.aliasMappings) {
if (index != -1) {
@@ -403,8 +406,12 @@ final class AnnotationTypeMapping {
if (type.isAnnotation() || (type.isArray() && type.componentType().isAnnotation())) {
Class<? extends Annotation> annotationType =
(Class<? extends Annotation>) (type.isAnnotation() ? type : type.componentType());
if (annotationType != this.annotationType) {
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0);
// Ensure we have not yet visited the current nested annotation type, in order
// to avoid infinite recursion for JVM languages other than Java that support
// recursive annotation definitions.
if (visitedAnnotationTypes.add(annotationType)) {
AnnotationTypeMapping mapping =
AnnotationTypeMappings.forAnnotationType(annotationType, visitedAnnotationTypes).get(0);
if (mapping.isSynthesizable()) {
return true;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -20,8 +20,10 @@ import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.lang.Nullable;
import org.springframework.util.ConcurrentReferenceHashMap;
@@ -40,6 +42,7 @@ import org.springframework.util.ConcurrentReferenceHashMap;
* be searched once, regardless of how many times they are actually used.
*
* @author Phillip Webb
* @author Sam Brannen
* @since 5.2
* @see AnnotationTypeMapping
*/
@@ -60,19 +63,21 @@ final class AnnotationTypeMappings {
private AnnotationTypeMappings(RepeatableContainers repeatableContainers,
AnnotationFilter filter, Class<? extends Annotation> annotationType) {
AnnotationFilter filter, Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
this.repeatableContainers = repeatableContainers;
this.filter = filter;
this.mappings = new ArrayList<>();
addAllMappings(annotationType);
addAllMappings(annotationType, visitedAnnotationTypes);
this.mappings.forEach(AnnotationTypeMapping::afterAllMappingsSet);
}
private void addAllMappings(Class<? extends Annotation> annotationType) {
private void addAllMappings(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
Deque<AnnotationTypeMapping> queue = new ArrayDeque<>();
addIfPossible(queue, null, annotationType, null);
addIfPossible(queue, null, annotationType, null, visitedAnnotationTypes);
while (!queue.isEmpty()) {
AnnotationTypeMapping mapping = queue.removeFirst();
this.mappings.add(mapping);
@@ -102,14 +107,15 @@ final class AnnotationTypeMappings {
}
private void addIfPossible(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping source, Annotation ann) {
addIfPossible(queue, source, ann.annotationType(), ann);
addIfPossible(queue, source, ann.annotationType(), ann, new HashSet<>());
}
private void addIfPossible(Deque<AnnotationTypeMapping> queue, @Nullable AnnotationTypeMapping source,
Class<? extends Annotation> annotationType, @Nullable Annotation ann) {
Class<? extends Annotation> annotationType, @Nullable Annotation ann,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
try {
queue.addLast(new AnnotationTypeMapping(source, annotationType, ann));
queue.addLast(new AnnotationTypeMapping(source, annotationType, ann, visitedAnnotationTypes));
}
catch (Exception ex) {
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
@@ -166,20 +172,22 @@ final class AnnotationTypeMappings {
* @return type mappings for the annotation type
*/
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType) {
return forAnnotationType(annotationType, AnnotationFilter.PLAIN);
return forAnnotationType(annotationType, new HashSet<>());
}
/**
* Create {@link AnnotationTypeMappings} for the specified annotation type.
* @param annotationType the source annotation type
* @param annotationFilter the annotation filter used to limit which
* annotations are considered
* @param visitedAnnotationTypes the set of annotations that we have already
* visited; used to avoid infinite recursion for recursive annotations which
* some JVM languages support (such as Kotlin)
* @return type mappings for the annotation type
*/
static AnnotationTypeMappings forAnnotationType(
Class<? extends Annotation> annotationType, AnnotationFilter annotationFilter) {
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
return forAnnotationType(annotationType, RepeatableContainers.standardRepeatables(), annotationFilter);
return forAnnotationType(annotationType, RepeatableContainers.standardRepeatables(),
AnnotationFilter.PLAIN, visitedAnnotationTypes);
}
/**
@@ -194,15 +202,35 @@ final class AnnotationTypeMappings {
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,
RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {
return forAnnotationType(annotationType, repeatableContainers, annotationFilter, new HashSet<>());
}
/**
* Create {@link AnnotationTypeMappings} for the specified annotation type.
* @param annotationType the source annotation type
* @param repeatableContainers the repeatable containers that may be used by
* the meta-annotations
* @param annotationFilter the annotation filter used to limit which
* annotations are considered
* @param visitedAnnotationTypes the set of annotations that we have already
* visited; used to avoid infinite recursion for recursive annotations which
* some JVM languages support (such as Kotlin)
* @return type mappings for the annotation type
*/
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,
RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
if (repeatableContainers == RepeatableContainers.standardRepeatables()) {
return standardRepeatablesCache.computeIfAbsent(annotationFilter,
key -> new Cache(repeatableContainers, key)).get(annotationType);
key -> new Cache(repeatableContainers, key)).get(annotationType, visitedAnnotationTypes);
}
if (repeatableContainers == RepeatableContainers.none()) {
return noRepeatablesCache.computeIfAbsent(annotationFilter,
key -> new Cache(repeatableContainers, key)).get(annotationType);
key -> new Cache(repeatableContainers, key)).get(annotationType, visitedAnnotationTypes);
}
return new AnnotationTypeMappings(repeatableContainers, annotationFilter, annotationType);
return new AnnotationTypeMappings(repeatableContainers, annotationFilter, annotationType,
visitedAnnotationTypes);
}
static void clearCache() {
@@ -235,14 +263,20 @@ final class AnnotationTypeMappings {
/**
* Get or create {@link AnnotationTypeMappings} for the specified annotation type.
* @param annotationType the annotation type
* @param visitedAnnotationTypes the set of annotations that we have already
* visited; used to avoid infinite recursion for recursive annotations which
* some JVM languages support (such as Kotlin)
* @return a new or existing {@link AnnotationTypeMappings} instance
*/
AnnotationTypeMappings get(Class<? extends Annotation> annotationType) {
return this.mappings.computeIfAbsent(annotationType, this::createMappings);
AnnotationTypeMappings get(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
return this.mappings.computeIfAbsent(annotationType, key -> createMappings(key, visitedAnnotationTypes));
}
AnnotationTypeMappings createMappings(Class<? extends Annotation> annotationType) {
return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType);
private AnnotationTypeMappings createMappings(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType,
visitedAnnotationTypes);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -98,7 +98,6 @@ abstract class AnnotationsScanner {
}
@Nullable
@SuppressWarnings("deprecation")
private static <C, R> R processClass(C context, Class<?> source, SearchStrategy searchStrategy,
Predicate<Class<?>> searchEnclosingClass, AnnotationsProcessor<C, R> processor) {
@@ -237,7 +236,6 @@ abstract class AnnotationsScanner {
}
@Nullable
@SuppressWarnings("deprecation")
private static <C, R> R processMethod(C context, Method source,
SearchStrategy searchStrategy, AnnotationsProcessor<C, R> processor) {
@@ -509,7 +507,6 @@ abstract class AnnotationsScanner {
return (type.getName().startsWith("java.") || type == Ordered.class);
}
@SuppressWarnings("deprecation")
private static boolean isWithoutHierarchy(AnnotatedElement source, SearchStrategy searchStrategy,
Predicate<Class<?>> searchEnclosingClass) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -45,7 +45,6 @@ import org.springframework.util.MimeType;
* @since 5.0
* @param <T> the element type
*/
@SuppressWarnings("deprecation")
public abstract class AbstractDataBufferDecoder<T> extends AbstractDecoder<T> {
private int maxInMemorySize = 256 * 1024;
@@ -42,14 +42,14 @@ import org.springframework.util.MimeTypeUtils;
*/
public final class CharBufferDecoder extends AbstractCharSequenceDecoder<CharBuffer> {
public CharBufferDecoder(List<String> delimiters, boolean stripDelimiter, MimeType... mimeTypes) {
super(delimiters, stripDelimiter, mimeTypes);
}
@Override
public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) {
return elementType.resolve() == CharBuffer.class && super.canDecode(elementType, mimeType);
return (elementType.resolve() == CharBuffer.class) && super.canDecode(elementType, mimeType);
}
@Override
@@ -69,9 +69,8 @@ public final class CharBufferDecoder extends AbstractCharSequenceDecoder<CharBuf
/**
* Create a {@code CharBufferDecoder} for {@code "text/plain"}.
* @param delimiters delimiter strings to use to split the input stream
* @param stripDelimiter whether to remove delimiters from the resulting
* input strings
* @param delimiters delimiter strings to use to split the input stream
* @param stripDelimiter whether to remove delimiters from the resulting input strings
*/
public static CharBufferDecoder textPlainOnly(List<String> delimiters, boolean stripDelimiter) {
var textPlain = new MimeType("text", "plain", DEFAULT_CHARSET);
@@ -87,9 +86,8 @@ public final class CharBufferDecoder extends AbstractCharSequenceDecoder<CharBuf
/**
* Create a {@code CharBufferDecoder} that supports all MIME types.
* @param delimiters delimiter strings to use to split the input stream
* @param stripDelimiter whether to remove delimiters from the resulting
* input strings
* @param delimiters delimiter strings to use to split the input stream
* @param stripDelimiter whether to remove delimiters from the resulting input strings
*/
public static CharBufferDecoder allMimeTypes(List<String> delimiters, boolean stripDelimiter) {
var textPlain = new MimeType("text", "plain", DEFAULT_CHARSET);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@@ -51,8 +51,7 @@ public final class CharSequenceEncoder extends AbstractEncoder<CharSequence> {
*/
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private final ConcurrentMap<Charset, Float> charsetToMaxBytesPerChar =
new ConcurrentHashMap<>(3);
private final ConcurrentMap<Charset, Float> charsetToMaxBytesPerChar = new ConcurrentHashMap<>(3);
private CharSequenceEncoder(MimeType... mimeTypes) {
@@ -105,8 +104,8 @@ public final class CharSequenceEncoder extends AbstractEncoder<CharSequence> {
}
int calculateCapacity(CharSequence sequence, Charset charset) {
float maxBytesPerChar = this.charsetToMaxBytesPerChar
.computeIfAbsent(charset, cs -> cs.newEncoder().maxBytesPerChar());
float maxBytesPerChar = this.charsetToMaxBytesPerChar.computeIfAbsent(charset,
cs -> cs.newEncoder().maxBytesPerChar());
float maxBytesForSequence = sequence.length() * maxBytesPerChar;
return (int) Math.ceil(maxBytesForSequence);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 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.
@@ -29,7 +29,7 @@ import org.springframework.util.StringUtils;
/**
* Converts a comma-delimited String to an Array.
* Only matches if String.class can be converted to the target array element type.
* Only matches if {@code String.class} can be converted to the target array element type.
*
* @author Keith Donald
* @author Juergen Hoeller
@@ -25,9 +25,8 @@ import org.springframework.util.StringUtils;
/**
* Abstract base class for {@link PropertySource} implementations backed by command line
* arguments. The parameterized type {@code T} represents the underlying source of command
* line options. This may be as simple as a String array in the case of
* {@link SimpleCommandLinePropertySource}, or specific to a particular API such as JOpt's
* {@code OptionSet} in the case of {@link JOptCommandLinePropertySource}.
* line options. For instance, {@link SimpleCommandLinePropertySource} uses a String
* array.
*
* <h3>Purpose and General Usage</h3>
*
@@ -203,7 +202,6 @@ import org.springframework.util.StringUtils;
* @param <T> the source type
* @see PropertySource
* @see SimpleCommandLinePropertySource
* @see JOptCommandLinePropertySource
*/
public abstract class CommandLinePropertySource<T> extends EnumerablePropertySource<T> {
@@ -74,15 +74,13 @@ import org.springframework.util.StringUtils;
*
* <h3>Beyond the basics</h3>
*
* <p>When more fully-featured command line parsing is necessary, consider using
* the provided {@link JOptCommandLinePropertySource}, or implement your own
* {@code CommandLinePropertySource} against the command line parsing library of your
* choice.
* <p>When more fully-featured command line parsing is necessary, consider
* implementing your own {@code CommandLinePropertySource} against the command line
* parsing library of your choice.
*
* @author Chris Beams
* @since 3.1
* @see CommandLinePropertySource
* @see JOptCommandLinePropertySource
*/
public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {
@@ -829,7 +829,7 @@ public abstract class DataBufferUtils {
*/
private static class SingleByteMatcher implements NestedMatcher {
static SingleByteMatcher NEWLINE_MATCHER = new SingleByteMatcher(new byte[] {10});
static final SingleByteMatcher NEWLINE_MATCHER = new SingleByteMatcher(new byte[] {10});
private final byte[] delimiter;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -95,7 +95,7 @@ public class LimitedDataBufferList extends ArrayList<DataBuffer> {
}
private void raiseLimitException() {
// Do not release here, it's likely down via doOnDiscard..
// Do not release here, it's likely done via doOnDiscard
throw new DataBufferLimitException(
"Exceeded limit on max bytes to buffer : " + this.maxByteCount);
}
@@ -313,7 +313,7 @@ public class NettyDataBuffer implements PooledDataBuffer {
Assert.notNull(dest, "Dest must not be null");
dest = dest.duplicate().clear();
dest.put(destPos, this.byteBuf.nioBuffer(), srcPos, length);
dest.put(destPos, this.byteBuf.nioBuffer(srcPos, length), 0, length);
}
@Override
@@ -54,7 +54,7 @@ final class OutputStreamPublisher implements Publisher<DataBuffer> {
private final int chunkSize;
public OutputStreamPublisher(Consumer<OutputStream> outputStreamConsumer, DataBufferFactory bufferFactory,
OutputStreamPublisher(Consumer<OutputStream> outputStreamConsumer, DataBufferFactory bufferFactory,
Executor executor, int chunkSize) {
this.outputStreamConsumer = outputStreamConsumer;
@@ -68,8 +68,8 @@ final class OutputStreamPublisher implements Publisher<DataBuffer> {
public void subscribe(Subscriber<? super DataBuffer> subscriber) {
Objects.requireNonNull(subscriber, "Subscriber must not be null");
OutputStreamSubscription subscription = new OutputStreamSubscription(subscriber, this.outputStreamConsumer,
this.bufferFactory, this.chunkSize);
OutputStreamSubscription subscription = new OutputStreamSubscription(
subscriber, this.outputStreamConsumer, this.bufferFactory, this.chunkSize);
subscriber.onSubscribe(subscription);
this.executor.execute(subscription::invokeHandler);
@@ -80,7 +80,6 @@ final class OutputStreamPublisher implements Publisher<DataBuffer> {
private static final Object READY = new Object();
private final Subscriber<? super DataBuffer> actual;
private final Consumer<OutputStream> outputStreamHandler;
@@ -98,8 +97,7 @@ final class OutputStreamPublisher implements Publisher<DataBuffer> {
private long produced;
public OutputStreamSubscription(Subscriber<? super DataBuffer> actual,
OutputStreamSubscription(Subscriber<? super DataBuffer> actual,
Consumer<OutputStream> outputStreamConsumer, DataBufferFactory bufferFactory, int chunkSize) {
this.actual = actual;
@@ -351,4 +349,5 @@ final class OutputStreamPublisher implements Publisher<DataBuffer> {
return res;
}
}
}
@@ -490,8 +490,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
String filePath = new File(path).getAbsolutePath();
int prefixIndex = filePath.indexOf(':');
if (prefixIndex == 1) {
// Possibly "c:" drive prefix on Windows, to be upper-cased for proper duplicate detection
filePath = StringUtils.capitalize(filePath);
// Possibly a drive prefix on Windows (for example, "c:"), so we prepend a slash
// and convert the drive letter to uppercase for consistent duplicate detection.
filePath = "/" + StringUtils.capitalize(filePath);
}
// Since '#' can appear in directories/filenames, java.net.URL should not treat it as a fragment
filePath = StringUtils.replace(filePath, "#", "%23");
@@ -65,7 +65,6 @@ public class DefaultDeserializer implements Deserializer<Object> {
* @see ObjectInputStream#readObject()
*/
@Override
@SuppressWarnings("resource")
public Object deserialize(InputStream inputStream) throws IOException {
ObjectInputStream objectInputStream = new ConfigurableObjectInputStream(inputStream, this.classLoader);
try {
@@ -22,8 +22,9 @@ import io.micrometer.context.ContextSnapshotFactory;
import org.springframework.core.task.TaskDecorator;
/**
* {@link TaskDecorator} that {@link ContextSnapshot#wrap(Runnable) wrap the execution} of
* tasks, assisting with context propagation.
* {@link TaskDecorator} that {@link ContextSnapshot#wrap(Runnable) wraps the execution}
* of tasks, assisting with context propagation.
*
* <p>This operation is only useful when the task execution is scheduled on a different
* thread than the original call stack; this depends on the choice of
* {@link org.springframework.core.task.TaskExecutor}. This is particularly useful for
@@ -39,6 +40,7 @@ public class ContextPropagatingTaskDecorator implements TaskDecorator {
private final ContextSnapshotFactory factory;
/**
* Create a new decorator that uses a default instance of the {@link ContextSnapshotFactory}.
*/
@@ -54,6 +56,7 @@ public class ContextPropagatingTaskDecorator implements TaskDecorator {
this.factory = factory;
}
@Override
public Runnable decorate(Runnable runnable) {
return this.factory.captureAll().wrap(runnable);
@@ -34,7 +34,7 @@ import org.springframework.util.Assert;
*
* <p><b>NOTE:</b> This ExecutorService adapter does <em>not</em> support the
* lifecycle methods in the {@code java.util.concurrent.ExecutorService} API
* ("shutdown()" etc), similar to a server-wide {@code ManagedExecutorService}
* ("shutdown()" etc.), similar to a server-wide {@code ManagedExecutorService}
* in a Jakarta EE environment. The lifecycle is always up to the backend pool,
* with this adapter acting as an access-only proxy for that target pool.
*
@@ -94,7 +94,7 @@ class MergedAnnotationReadingVisitor<A extends Annotation> extends AnnotationVis
@Override
public void visitEnd() {
Map<String, Object> compactedAttributes =
(this.attributes.size() == 0 ? Collections.emptyMap() : this.attributes);
(this.attributes.isEmpty() ? Collections.emptyMap() : this.attributes);
MergedAnnotation<A> annotation = MergedAnnotation.of(
this.classLoader, this.source, this.annotationType, compactedAttributes);
this.consumer.accept(annotation);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -73,7 +73,7 @@ public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilte
// Optimization to avoid creating ClassReader for superclass.
Boolean superClassMatch = matchSuperClass(superClassName);
if (superClassMatch != null) {
if (superClassMatch.booleanValue()) {
if (superClassMatch) {
return true;
}
}
@@ -99,7 +99,7 @@ public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilte
// Optimization to avoid creating ClassReader for superclass
Boolean interfaceMatch = matchInterface(ifc);
if (interfaceMatch != null) {
if (interfaceMatch.booleanValue()) {
if (interfaceMatch) {
return true;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -402,7 +402,7 @@ public class AntPathMatcher implements PathMatcher {
protected String[] tokenizePattern(String pattern) {
String[] tokenized = null;
Boolean cachePatterns = this.cachePatterns;
if (cachePatterns == null || cachePatterns.booleanValue()) {
if (cachePatterns == null || cachePatterns) {
tokenized = this.tokenizedPatternCache.get(pattern);
}
if (tokenized == null) {
@@ -414,7 +414,7 @@ public class AntPathMatcher implements PathMatcher {
deactivatePatternCache();
return tokenized;
}
if (cachePatterns == null || cachePatterns.booleanValue()) {
if (cachePatterns == null || cachePatterns) {
this.tokenizedPatternCache.put(pattern, tokenized);
}
}
@@ -458,7 +458,7 @@ public class AntPathMatcher implements PathMatcher {
protected AntPathStringMatcher getStringMatcher(String pattern) {
AntPathStringMatcher matcher = null;
Boolean cachePatterns = this.cachePatterns;
if (cachePatterns == null || cachePatterns.booleanValue()) {
if (cachePatterns == null || cachePatterns) {
matcher = this.stringMatcherCache.get(pattern);
}
if (matcher == null) {
@@ -470,7 +470,7 @@ public class AntPathMatcher implements PathMatcher {
deactivatePatternCache();
return matcher;
}
if (cachePatterns == null || cachePatterns.booleanValue()) {
if (cachePatterns == null || cachePatterns) {
this.stringMatcherCache.put(pattern, matcher);
}
}
@@ -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.
@@ -138,7 +138,7 @@ public class AutoPopulatingList<E> implements List<E>, Serializable {
@Override
public E get(int index) {
int backingListSize = this.backingList.size();
E element = null;
E element;
if (index < backingListSize) {
element = this.backingList.get(index);
if (element == null) {
@@ -98,7 +98,7 @@ public abstract class Base64Utils {
}
/**
* Base64-decode the given byte array from an UTF-8 String.
* Base64-decode the given byte array from a UTF-8 String.
* @param src the encoded UTF-8 String
* @return the original byte array
*/
@@ -120,7 +120,7 @@ public abstract class Base64Utils {
}
/**
* Base64-decode the given byte array from an UTF-8 String using the RFC 4648
* Base64-decode the given byte array from a UTF-8 String using the RFC 4648
* "URL and Filename Safe Alphabet".
* @param src the encoded UTF-8 String
* @return the original byte array
@@ -106,19 +106,19 @@ public abstract class ClassUtils {
/**
* Map with primitive wrapper type as key and corresponding primitive
* type as value, for example: Integer.class -> int.class.
* type as value, for example: {@code Integer.class -> int.class}.
*/
private static final Map<Class<?>, Class<?>> primitiveWrapperTypeMap = new IdentityHashMap<>(9);
/**
* Map with primitive type as key and corresponding wrapper
* type as value, for example: int.class -> Integer.class.
* type as value, for example: {@code int.class -> Integer.class}.
*/
private static final Map<Class<?>, Class<?>> primitiveTypeToWrapperMap = new IdentityHashMap<>(9);
/**
* Map with primitive type name as key and corresponding primitive
* type as value, for example: "int" -> "int.class".
* type as value, for example: {@code "int" -> int.class}.
*/
private static final Map<String, Class<?>> primitiveTypeNameMap = new HashMap<>(32);
@@ -396,7 +396,6 @@ public final class ConcurrentLruCache<K, V> {
private final EvictionQueue<K, V> evictionQueue;
@SuppressWarnings("rawtypes")
ReadOperations(EvictionQueue<K, V> evictionQueue) {
this.evictionQueue = evictionQueue;
for (int i = 0; i < BUFFER_COUNT; i++) {
@@ -317,7 +317,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
}
@Override
public boolean remove(@Nullable Object key, final @Nullable Object value) {
public boolean remove(@Nullable Object key, @Nullable final Object value) {
Boolean result = doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
@Override
protected Boolean execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
@@ -334,7 +334,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
}
@Override
public boolean replace(@Nullable K key, final @Nullable V oldValue, final @Nullable V newValue) {
public boolean replace(@Nullable K key, @Nullable final V oldValue, @Nullable final V newValue) {
Boolean result = doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
@Override
protected Boolean execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
@@ -350,7 +350,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
@Override
@Nullable
public V replace(@Nullable K key, final @Nullable V value) {
public V replace(@Nullable K key, @Nullable final V value) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
@Override
@Nullable
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -125,13 +125,13 @@ public abstract class DigestUtils {
private static byte[] digest(String algorithm, InputStream inputStream) throws IOException {
MessageDigest messageDigest = getDigest(algorithm);
if (inputStream instanceof UpdateMessageDigestInputStream digestIntputStream){
digestIntputStream.updateMessageDigest(messageDigest);
if (inputStream instanceof UpdateMessageDigestInputStream digestInputStream){
digestInputStream.updateMessageDigest(messageDigest);
return messageDigest.digest();
}
else {
final byte[] buffer = new byte[StreamUtils.BUFFER_SIZE];
int bytesRead = -1;
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
messageDigest.update(buffer, 0, bytesRead);
}
@@ -84,12 +84,13 @@ public abstract class FileSystemUtils {
return false;
}
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
Files.walkFileTree(root, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
@@ -126,12 +127,13 @@ public abstract class FileSystemUtils {
BasicFileAttributes srcAttr = Files.readAttributes(src, BasicFileAttributes.class);
if (srcAttr.isDirectory()) {
Files.walkFileTree(src, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
Files.walkFileTree(src, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Files.createDirectories(dest.resolve(src.relativize(dir)));
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.copy(file, dest.resolve(src.relativize(file)), StandardCopyOption.REPLACE_EXISTING);
@@ -102,7 +102,6 @@ public class MimeType implements Comparable<MimeType>, Serializable {
private final String subtype;
@SuppressWarnings("serial")
private final Map<String, String> parameters;
@Nullable
@@ -642,7 +641,7 @@ public class MimeType implements Comparable<MimeType>, Serializable {
}
/**
* Indicates whether this {@code MimeType} is more less than the given type.
* Indicates whether this {@code MimeType} is less specific than the given type.
* <ol>
* <li>if this mime type has a {@linkplain #isWildcardType() wildcard type},
* and the other does not, then this method returns {@code true}.</li>
@@ -684,7 +683,7 @@ public class MimeType implements Comparable<MimeType>, Serializable {
/**
* Parse the given String value into a {@code MimeType} object,
* with this method name following the 'valueOf' naming convention
* (as supported by {@link org.springframework.core.convert.ConversionService}.
* (as supported by {@link org.springframework.core.convert.ConversionService}).
* @see MimeTypeUtils#parseMimeType(String)
*/
public static MimeType valueOf(String value) {
@@ -362,7 +362,9 @@ public abstract class MimeTypeUtils {
*/
public static <T extends MimeType> void sortBySpecificity(List<T> mimeTypes) {
Assert.notNull(mimeTypes, "'mimeTypes' must not be null");
Assert.isTrue(mimeTypes.size() <= 50, "Too many elements");
if (mimeTypes.size() >= 50) {
throw new InvalidMimeTypeException(mimeTypes.toString(), "Too many elements");
}
bubbleSort(mimeTypes, MimeType::isLessSpecific);
}
@@ -144,7 +144,7 @@ public abstract class ObjectUtils {
return optional.isEmpty();
}
if (obj instanceof CharSequence charSequence) {
return charSequence.length() == 0;
return charSequence.isEmpty();
}
if (obj.getClass().isArray()) {
return Array.getLength(obj) == 0;
@@ -28,9 +28,10 @@ import org.springframework.lang.Nullable;
public abstract class PatternMatchUtils {
/**
* Match a String against the given pattern, supporting the following simple
* pattern styles: {@code xxx*}, {@code *xxx}, {@code *xxx*}, and {@code xxx*yyy}
* matches (with an arbitrary number of pattern parts), as well as direct equality.
* Match a String against the given pattern, supporting direct equality as
* well as the following simple pattern styles: {@code xxx*}, {@code *xxx},
* {@code *xxx*}, and {@code xxx*yyy} (with an arbitrary number of pattern parts).
* <p>Returns {@code false} if the supplied String or pattern is {@code null}.
* @param pattern the pattern to match against
* @param str the String to match
* @return whether the String matches the given pattern
@@ -73,14 +74,16 @@ public abstract class PatternMatchUtils {
}
/**
* Match a String against the given patterns, supporting the following simple
* pattern styles: {@code xxx*}, {@code *xxx}, {@code *xxx*}, and {@code xxx*yyy}
* matches (with an arbitrary number of pattern parts), as well as direct equality.
* Match a String against the given patterns, supporting direct equality as
* well as the following simple pattern styles: {@code xxx*}, {@code *xxx},
* {@code *xxx*}, and {@code xxx*yyy} (with an arbitrary number of pattern parts).
* <p>Returns {@code false} if the supplied String is {@code null} or if the
* supplied patterns array is {@code null} or empty.
* @param patterns the patterns to match against
* @param str the String to match
* @return whether the String matches any of the given patterns
*/
public static boolean simpleMatch(@Nullable String[] patterns, String str) {
public static boolean simpleMatch(@Nullable String[] patterns, @Nullable String str) {
if (patterns != null) {
for (String pattern : patterns) {
if (simpleMatch(pattern, str)) {
@@ -839,7 +839,7 @@ public abstract class StringUtils {
* the {@link Locale#toString} format as well as BCP 47 language tags as
* specified by {@link Locale#forLanguageTag}.
* @param localeValue the locale value: following either {@code Locale's}
* {@code toString()} format ("en", "en_UK", etc), also accepting spaces as
* {@code toString()} format ("en", "en_UK", etc.), also accepting spaces as
* separators (as an alternative to underscores), or BCP 47 (e.g. "en-UK")
* @return a corresponding {@code Locale} instance, or {@code null} if none
* @throws IllegalArgumentException in case of an invalid locale specification
@@ -868,7 +868,7 @@ public abstract class StringUtils {
* <p><b>Note: This delegate does not accept the BCP 47 language tag format.
* Please use {@link #parseLocale} for lenient parsing of both formats.</b>
* @param localeString the locale {@code String}: following {@code Locale's}
* {@code toString()} format ("en", "en_UK", etc), also accepting spaces as
* {@code toString()} format ("en", "en_UK", etc.), also accepting spaces as
* separators (as an alternative to underscores)
* @return a corresponding {@code Locale} instance, or {@code null} if none
* @throws IllegalArgumentException in case of an invalid locale specification
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 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.
@@ -26,7 +26,6 @@ import org.springframework.lang.Nullable;
* @since 2.5
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory#resolveAliases
* @see org.springframework.beans.factory.config.BeanDefinitionVisitor#BeanDefinitionVisitor(StringValueResolver)
* @see org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
*/
@FunctionalInterface
public interface StringValueResolver {
@@ -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.
@@ -35,13 +35,13 @@ import org.springframework.lang.Nullable;
*/
public abstract class SystemPropertyUtils {
/** Prefix for system property placeholders: "${". */
/** Prefix for system property placeholders: {@value}. */
public static final String PLACEHOLDER_PREFIX = "${";
/** Suffix for system property placeholders: "}". */
/** Suffix for system property placeholders: {@value}. */
public static final String PLACEHOLDER_SUFFIX = "}";
/** Value separator for system property placeholders: ":". */
/** Value separator for system property placeholders: {@value}. */
public static final String VALUE_SEPARATOR = ":";
@@ -47,7 +47,6 @@ final class UnmodifiableMultiValueMap<K,V> implements MultiValueMap<K,V>, Serial
private static final long serialVersionUID = -8697084563854098920L;
@SuppressWarnings("serial")
private final MultiValueMap<K, V> delegate;
@Nullable
@@ -266,7 +265,6 @@ final class UnmodifiableMultiValueMap<K,V> implements MultiValueMap<K,V>, Serial
private static final long serialVersionUID = 2407578793783925203L;
@SuppressWarnings("serial")
private final Set<Entry<K, List<V>>> delegate;
@SuppressWarnings("unchecked")
@@ -516,7 +514,6 @@ final class UnmodifiableMultiValueMap<K,V> implements MultiValueMap<K,V>, Serial
private static final long serialVersionUID = 5518377583904339588L;
@SuppressWarnings("serial")
private final Collection<List<V>> delegate;
public UnmodifiableValueCollection(Collection<List<V>> delegate) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -54,7 +54,7 @@ public abstract class ListenableFutureAdapter<T, S> extends FutureAdapter<T, S>
@Override
public void addCallback(final SuccessCallback<? super T> successCallback, final FailureCallback failureCallback) {
ListenableFuture<S> listenableAdaptee = (ListenableFuture<S>) getAdaptee();
listenableAdaptee.addCallback(new ListenableFutureCallback<S>() {
listenableAdaptee.addCallback(new ListenableFutureCallback<>() {
@Override
public void onSuccess(@Nullable S result) {
T adapted = null;
@@ -74,6 +74,7 @@ public abstract class ListenableFutureAdapter<T, S> extends FutureAdapter<T, S>
}
successCallback.onSuccess(adapted);
}
@Override
public void onFailure(Throwable ex) {
failureCallback.onFailure(ex);
@@ -49,7 +49,7 @@ public class SingletonSupplier<T> implements Supplier<T> {
private volatile T singletonInstance;
/**
* Guards access to write operations on the response.
* Guards access to write operations on the {@code singletonInstance} field.
*/
private final Lock writeLock = new ReentrantLock();

Some files were not shown because too many files have changed in this diff Show More