Compare commits

...

128 Commits

Author SHA1 Message Date
Spring Builds 3540029931 Release v5.3.26 2023-03-20 09:52:18 +00:00
rstoyanchev eafe3afe11 Polishing and minor refactoring in HandlerMappingIntrospector
Closes gh-30128
2023-03-20 08:38:11 +00:00
Sam Brannen 26e0343c16 Improve diagnostics in SpEL for matches operator
Supplying a large regular expression to the `matches` operator in a
SpEL expression can result in errors that are not very helpful to the
user.

This commit improves the diagnostics in SpEL for the `matches` operator
by throwing a SpelEvaluationException with a meaningful error message
to better assist the user.

Closes gh-30145
2023-03-20 00:07:01 +01:00
Sam Brannen 4d5e7207f2 Improve diagnostics in SpEL for repeated text
Attempting to create repeated text in a SpEL expression using the
repeat operator can result in errors that are not very helpful to the
user.

This commit improves the diagnostics in SpEL for the repeat operator by
throwing a SpelEvaluationException with a meaningful error message in
order to better assist the user.

Closes gh-30143
2023-03-20 00:06:54 +01:00
Sam Brannen 430fc25aca Increase scope of regex pattern cache for the SpEL matches operator
Prior to this commit, the pattern cache for the SpEL `matches` operator
only applied to expressions such as the following where the same
`matches` operator is invoked multiple times with different input:

  "map.keySet().?[#this matches '.+xyz']"

The pattern cache did not apply to expressions such as the following
where the same pattern ('.+xyz') is used in multiple `matches`
operations:

  "foo matches '.+xyz' AND bar matches '.+xyz'"

This commit addresses this by moving the instance of the pattern cache
map from OperatorMatches to InternalSpelExpressionParser so that the
cache can be reused for all `matches` operations for the given parser.

Closes gh-30141
2023-03-20 00:06:46 +01:00
Sam Brannen 0882ca57d4 Polishing 2023-03-20 00:06:32 +01:00
Sam Brannen 94bbf85c0e Stop printing to System.out in SpEL tests 2023-03-20 00:06:24 +01:00
Juergen Hoeller 2c2ef12f68 Upgrade to Netty 4.1.90 and Checkstyle 10.9.1 2023-03-17 18:10:02 +01:00
Juergen Hoeller 120d512ff6 Polishing (backported from main) 2023-03-17 18:09:46 +01:00
Sam Brannen 3ddf183922 Update copyright headers 2023-03-17 14:51:13 +01:00
rstoyanchev 4c69bfd32f Upgrade to Reactor 2020.0.30
Closes gh-30116
2023-03-16 08:30:16 +00:00
Sam Brannen 41d71e9a7f Revise contribution
See gh-25316
2023-03-15 14:30:53 +01:00
mrcoffee77 36682b7ad2 Ensure methods declared in Object can be invoked on a JDK proxy in SpEL
This commit ensures that methods declared in java.lang.Object (such as
toString() can be invoked on a JDK proxy instance in a SpEL expression.

Closes gh-25316
2023-03-15 14:30:53 +01:00
Sam Brannen 30601a5014 Polishing 2023-03-15 14:30:53 +01:00
Brian Clozel f2371f5e7d Ignore quality factor when filtering out "*/*"
Prior to this commit, the `RequestedContentTypeResolverBuilder` would
create a `RequestedContentTypeResolver` that internally delegates to a
list of resolvers. Each resolver would either return the list of
requested media types, or a singleton list with the "*/*" media type; in
this case this signals that the resolver cannot find a specific media
type requested and that we should continue with the next resolver in the
list.

Media Types returned by resolvers can contain parameters, such as the
quality factor. If the HTTP client requests "*/*;q=0.8", the
`HeaderContentTypeResolver` will return this as a singleton list. While
this has been resolved from the request, such a media type should not be
selected over other media types that could be returned by other
resolvers.

This commit changes the `RequestedContentTypeResolverBuilder` so that it
does not select "*/*;q=0.8" as the requested media type, but instead
continues delegating to other resolvers in the list. This means we need
to remove the quality factor before comparing it to the "*/*" for
equality check.

Fixes gh-30121
2023-03-15 10:45:31 +01:00
rstoyanchev f0da099b12 Prefer request hostName and hostPort in ReactorServerHttpRequest
Backport of 682a4d53 and 9624ea39

Closes gh-29974
2023-03-14 07:02:21 +00:00
Brian Clozel d00fd4c502 Allow runtime compatibility with SnakeYaml 2.0
This commit ensures that SnakeYaml 2.0 is compatible at runtime with
Spring Framework 5.3.x with the `YamlProcessor` support.
The baseline version for SnakeYaml remains the same.

Closes gh-30097
2023-03-10 12:41:03 +01:00
Sébastien Deleuze 44a6d13cc0 Fix minor spacings in webflux docs
Closes gh-30095
2023-03-09 11:56:55 +01:00
Juergen Hoeller 284657355a Upgrade to Log4J 2.20, Tomcat 9.0.73, Jetty 9.4.51, Undertow 2.2.23 2023-03-08 17:44:10 +01:00
Juergen Hoeller 6a81ed3a50 Polishing 2023-03-08 17:43:35 +01:00
Brian Clozel 0a053cfccb Avoid lock contention in CaffeineCacheManager
Prior to this commit, using a dynamic `CaffeineCacheManager` would rely
on `ConcurrentHashMap#computeIfAbsent` for retrieving and creating cache
instances as needed. It turns out that using this method concurrently
can cause lock contention even when all known cache instances are
instantiated.

This commit avoids using this method if the cache instance already
exists and avoid storing `null` entries in the map. This change reduces
lock contention and the overall HashMap size in the non-dynamic case.

See gh-30066
Fixes gh-30085
2023-03-08 16:27:50 +01:00
Sam Brannen 22bb76d326 Revise documentation for @AspectJ argument name resolution algorithm
Closes gh-30057
2023-03-02 17:22:17 +01:00
Sam Brannen 4dc45d551c Update documentation for @AspectJ argument name resolution algorithm
Closes gh-30057
2023-03-01 17:29:50 +01:00
Sam Brannen e5d05ddfc3 Remove obsolete MetadataAwareAspectInstanceFactory Javadoc
We no longer have any JDK 5 related limitations imposed on us as was
the case when MetadataAwareAspectInstanceFactory was introduced; however,
MetadataAwareAspectInstanceFactory will remain as a specialized
sub-interface of AspectInstanceFactory.
2023-03-01 17:13:55 +01:00
Sam Brannen 44a5f8ec06 Add missing package-info.java file for autoproxy.target package 2023-03-01 17:13:45 +01:00
Sam Brannen 0a8bda40f4 Fix .gitignore pattern for Maven "target" folders
Rationale: changes in org.springframework.aop.framework.autoproxy.target
were previously ignored since the "target" package was ignored by the
previous "eager" pattern for "target" folders.
2023-03-01 17:13:32 +01:00
Radek Kraus 8a879c6fed Protect JMS connection creation against prepareConnection errors
This commit uses a local variable for the creation of a new JMS
Connection so that a rare failure in prepareConnection(...) does not
leave the connection field in a partially initialized state.

If such a JMSException occurs, the intermediary connection is closed.
This commit further defends against close() failures at that point,
by logging the close exception at DEBUG level. As a result, the original
JMSException is always re-thrown.

See gh-29116
Closes gh-30051
2023-02-28 16:35:36 +01:00
Sam Brannen 28d11aaf64 Polish contribution
See gh-30036
2023-02-27 16:47:09 +01:00
1993heqiang 02941127e1 Fix "Configuring a Global Date and Time Format" example
Closes gh-30036
2023-02-27 16:47:03 +01:00
Sam Brannen b1b24458c9 Polishing 2023-02-27 16:46:16 +01:00
Sébastien Deleuze 854b625be2 Add missing @Nullable annotations to LogMessage methods
Closes gh-30009
2023-02-24 17:51:52 +01:00
rstoyanchev a2b7a907ec Prefer local hostAddress in ReactorServerHttpRequest
Closes gh-28601
2023-02-23 16:40:38 +00:00
Brian Clozel 4f0a8911ca Fix CI image resource in pipeline
This commit fixes the CI image resource configuration so that it can be
checked autmatically by the pipeline.
This also updates various resources.
2023-02-23 09:44:09 +01:00
Sam Brannen 40fef7b232 Fix Javadoc for MockRestRequestMatchers 2023-02-22 11:09:03 +01:00
Sam Brannen 423134f64c Revise queryParam() and header() support in MockRestRequestMatchers
See gh-29953
See gh-29964
2023-02-22 11:08:05 +01:00
Sam Brannen 574c10d219 Polishing 2023-02-22 11:08:05 +01:00
Johnny Lim 2b4b947050 Fix Javadoc since for MockRestRequestMatchers queryParam() and header() (#29986)
See gh-29953
See gh-29964
2023-02-22 11:08:05 +01:00
Juergen Hoeller 4993b1090a Polishing 2023-02-22 11:08:05 +01:00
Juergen Hoeller 3adabf391f Consistent ordering of Resource methods (backported from main) 2023-02-15 12:35:41 +01:00
Juergen Hoeller 0026338c00 Consistent @Bean method return type for equivalence with XML example
Closes gh-29970
2023-02-14 16:57:35 +01:00
Juergen Hoeller cdea667e58 Test for request attribute visibility in FreeMarker (backported from main)
See gh-29787
2023-02-14 16:57:22 +01:00
Juergen Hoeller 66b1c0b4b0 Upgrade to Netty 4.1.89 2023-02-14 11:38:32 +01:00
Juergen Hoeller 6e42d36614 ASM upgrade for JDK 20/21 support (backported from main)
Closes gh-29966
2023-02-14 11:38:12 +01:00
Simon Baslé 7f2c93fa1f Allow MockRest to match header/queryParam value list with one Matcher
This commit adds a `header` variant and a `queryParam` variant to the
`MockRestRequestMatchers` API which take a single `Matcher` over the
list of values.

Contrary to the vararg variants, the whole list is evaluated and the
caller can choose the desired semantics using readily-available iterable
matchers like `everyItem`, `hasItems`, `hasSize`, `contains` or
`containsInAnyOrder`...

The fact that the previous variants don't strictly check the size of the
actual list == the number of provided matchers or expected values is
now documented in their respective javadocs.

See gh-29953
Closes gh-29964
2023-02-13 17:53:48 +01:00
Johnny Lim 4c351e811a Polish
See gh-29928
2023-02-09 09:55:19 +01:00
rstoyanchev 5b67dea506 Unwrap session before selecting stats counter
See gh-29375
2023-02-08 18:10:11 +00:00
Sam Brannen b9fe095f60 Clearly document that DataClassRowMapper supports Java records
Closes gh-29814
2023-02-07 19:09:47 +01:00
Sam Brannen 6b17014b5a Polish RowMapper tests 2023-02-07 19:09:32 +01:00
Sam Brannen 878246b09b Avoid confusing terminology in BeanPropertyRowMapper
Prior to this commit, the term "field" was sometimes used to refer to a
database column and sometimes used to refer to a bean property, which
lead to confusion in the Javadoc as well as within the code.

This commit addresses this by avoiding use of the term "field".
2023-02-07 19:07:40 +01:00
Sam Brannen 9067ccab2d Update copyright headers 2023-02-07 16:35:52 +01:00
Manthan Bhatt 4e00aece7a Restrict forwards in MockMvcWebConnection to 100
This change restricts the maximum number of forwards in MockMvcWebConnection to 100,
in case a forward is configured in a way that causes a loop. This is necessary in HtmlUnit
backed tests, unlike in classic MockMvc tests in which the forwards are not actually resolved.

See gh-29557
Closes gh-29866

Co-authored-by: Simon Baslé <sbasle@vmware.com>
2023-02-07 16:24:57 +01:00
Sam Brannen c9841f37b6 Revise Testcontainers examples based on feedback
Closes gh-29940
2023-02-07 15:57:23 +01:00
Johnny Lim 92d513e9fb Add MockMvc.multipart() Kotlin extensions with HttpMethod
See gh-28545
See gh-28631
Closes gh-29941
2023-02-07 15:12:27 +01:00
Sam Brannen 782ee34cb7 Update @DynamicPropertySource examples regarding changes in Testcontainers
Closes gh-29939
2023-02-07 13:38:50 +01:00
Sam Brannen 80c10fad92 Polish RowMapper tests 2023-02-03 18:06:10 +01:00
Sam Brannen 498a0d286f Clarify semantics of primitivesDefaultedForNullValue in BeanPropertyRowMapper
Closes gh-29923
2023-02-03 18:06:10 +01:00
Sam Brannen e9d45d1b49 Polish Javadoc for RowMappers 2023-02-03 18:01:07 +01:00
danu 0851b0f72d Release R2DBC connection when cleanup fails in transaction
When using R2dbcTransactionManager, connection will not be released if
it encounters error while doing `afterCleanup` steps. As `afterCleanup`
can use a database connection when doing `setAutoCommit(true)`, it can
fail under some conditions where the connection is not reliable.

This leads to the Connection not being released.

This commit ensures that inner steps of the `doCleanupAfterCompletion`
are protected against errors, logging the errors and continuing the
cleanup until the last step, which releases the connection.

Backport of commit e050c37
See gh-29703
Closes gh-29925

Co-authored-by: Simon Baslé <sbasle@vmware.com>
2023-02-03 17:43:22 +01:00
Brian Clozel 91d991e86c Rely only on Docker Hub for fetching OCI images 2023-02-02 21:47:19 +01:00
Juergen Hoeller 960f6fb936 Add missing warn level check (backported from main) 2023-02-01 18:28:20 +01:00
Juergen Hoeller 4d3d34528e Upgrade to Tomcat 9.0.71, Netty 4.1.87, AssertJ 3.24.2, HtmlUnit 2.70, Checkstyle 10.7 2023-02-01 18:26:00 +01:00
Juergen Hoeller 78ba946266 Clarify postProcessBeanFactory lifecycle state
Closes gh-29064

(cherry picked from commit b8827d8e11)
2023-02-01 18:21:16 +01:00
Juergen Hoeller 42e7318cbb Polishing 2023-01-31 16:48:36 +01:00
Juergen Hoeller 000383fbff Explicit target ClassLoader for interface-based infrastructure proxies
Includes direct JDK Proxy usage instead of ProxyFactory where possible.

Closes gh-29913

(cherry picked from commit 4d6249811e)
2023-01-31 16:48:27 +01:00
Juergen Hoeller 37cbdc2cf4 Lazily load ContextLoader.properties (and lazily fail if not present)
Closes gh-29905

(cherry picked from commit a74b86e812)
2023-01-31 16:21:45 +01:00
Juergen Hoeller 6d95e7f9d9 Declare no-op close() method in order to avoid container-triggered shutdown call
Closes gh-29892

(cherry picked from commit 7c9dca3d2e)
2023-01-31 16:21:39 +01:00
rstoyanchev 45a7917a36 Backport of cf9fc69d6b
Closes gh-29911
2023-01-31 11:55:32 +00:00
rstoyanchev 25b95b8b98 Avoid ClassLoader issue in Jetty10 WebSocket upgrade
Closes gh-29256
2023-01-31 11:50:22 +00:00
Sam Brannen 6bd7a7321e Revise generated default name for @JmsListener subscription
The previous commit changed the generated default name for a JMS
subscription to <FQCN>#<method name> -- for example:

- org.example.MyListener#myListenerMethod

However, the JMS spec does not guarantee that '#' is a supported
character. This commit therefore changes '#' to '.' as the separator
between the class name and method name -- for example:

- org.example.MyListener.myListenerMethod

This commit also introduces tests and documentation for these changes.

See gh-29902
2023-01-30 19:57:28 +01:00
Sam Brannen 11aaba9877 Polishing 2023-01-30 19:52:24 +01:00
fml2 07fd7606e7 Improve generated default name for a @JmsListener subscription
Prior to this commit, when using durable subscribers with @JmsListener
methods that do not specify a custom subscription name the generated
default subscription name was always
org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.
Consequently, multiple such @JmsListener methods were assigned the
same subscription name which violates the uniqueness requirement.

To address this, MessagingMessageListenerAdapter now implements
SubscriptionNameProvider and generates the subscription name based on
the following rules.

- if the InvocableHandlerMethod is present, the subscription name will
  take the form of handlerMethod.getBeanType().getName() + "#" +
  handlerMethod.getMethod().getName().
- otherwise, getClass().getName() is used, which is analogous to the
  previous behavior.

Closes gh-29902
2023-01-30 19:52:15 +01:00
Sam Brannen 40d2466334 Fix build due to Jackson Javadoc publication changes
The Jackson project no longer publishes Javadoc at
https://fasterxml.github.io which breaks the `javadoc` and `api` build
tasks due to their dependency on that web site for external Javadoc links.

As a workaround, we now reference Jackson's Javadoc via
https://www.javadoc.io.

See https://github.com/FasterXML/jackson-databind/issues/3440
Closes gh-29895
2023-01-29 12:43:11 +01:00
Sam Brannen 3d6d853bbc Include all Hibernate methods in SharedEntityManagerCreator's queryTerminatingMethods
Prior to this commit, we included Hibernate's Query.list() method in
SharedEntityManagerCreator's queryTerminatingMethods set but did not
include all of Hibernate's query-terminating methods.

To address this, this commit additionally includes the stream(),
uniqueResult(), and uniqueResultOptional() methods from Hibernate's
Query API in SharedEntityManagerCreator's query-terminating methods set.

Closes gh-29888
2023-01-28 20:59:46 +01:00
Sam Brannen 90ea39cc6b Update copyright headers 2023-01-28 20:49:13 +01:00
Arjen Poutsma 21c3d4f4a9 Support Jetty 10 in JettyClientHttpRequest
Though Jetty 10 was previously supported in the JettyClientHttpResponse,
this commit ensures support in the JettyClientHttpRequest.

Closes gh-29867
See gh-26123
2023-01-26 17:27:57 +01:00
Simon Baslé de53d77344 DatabaseClient uses SQL Supplier more lazily
This commit modifies the `DefaultDatabaseClient` implementation in order
to ensure lazier usage of the `Supplier<String>` passed to the sql
method (`DatabaseClient#sql(Supplier)`).

Since technically `DatabaseClient` is an interface that could have 3rd
party implementations, the lazyness expectation is only hinted at in the
`DatabaseClient#sql` javadoc.

Possible caveat: some log statements attempt to reflect the now lazily
resolved SQL string. Similarly, some exceptions can capture the SQL that
caused the issue if known. We expect that these always occur after the
execution of the statement has been attempted (see `ResultFunction`).
At this point the SQL string will be accessible and logs and exceptions
should reflect it as before. Keep an eye out for such strings turning
into `null` after this change, which would indicate the opposite.

Backport of d72df5ace4
See gh-29367
Closes gh-29887
2023-01-26 15:20:38 +01:00
Sébastien Deleuze e4e90bbec0 Polish RouterFunctionDsl KDoc 2023-01-23 13:27:34 +01:00
Sébastien Deleuze d0828be0cd Remove WebClientIntegrationTests#exchangeWithRelativeUrl outdated test
This test can fail when a web server runs on port 80 and
is not relevant anymore due to the removal of related feature
via 6e936a4081.

Closes gh-29863
2023-01-20 12:24:41 +01:00
Sébastien Deleuze 4a4b332709 Refine Jackson2ObjectMapperBuilder#configureFeature exception handling
This commit changes the FatalBeanException previously thrown for
an IllegalArgumentException which seems more suitable for that
use case.

Closes gh-29860
2023-01-20 10:15:49 +01:00
Brian Clozel 33f1c9b614 Upgrade Gradle Enterprise & Conventions plugins
This is required to adapt to the repo.spring.io permission changes.
2023-01-19 11:34:05 +01:00
Arjen Poutsma 53ac812fb0 Updated sdkmanrc 2023-01-19 10:05:36 +01:00
Arjen Poutsma 60c89dd2df Fix IllegalStateException in empty ProducesRequestCondition
When comparing empty ProducesRequestCondition, compareTo would throw an
IllegalStateException if the Accept header was invalid. This commit
fixes that behavior.

See gh-29794
Closes gh-29836
2023-01-18 10:45:12 +01:00
Minsoo Cheong(Merlin) 4bd2531774 Fix R2dbcTransactionManager debug log: don't log a Mono (#29800)
When logging the current connection inside R2dbcTransactionManager
doBegin, the mono object was logged instead of the connection lambda
parameter.

Other similar debug-level logs do use the actual Connection object,
so this commit does the same.

Backport of gh-29800
Closes gh-29824
2023-01-16 11:30:35 +01:00
Spring Builds 4fd5630700 Next development version (v5.3.26-SNAPSHOT) 2023-01-11 11:19:35 +00:00
Juergen Hoeller 26cd33cb2b Upgrade to Reactor 2020.0.27
Includes HtmlUnit 2.69, AssertJ 3.24.1, Checkstyle 10.6

Closes gh-29798
2023-01-11 00:23:38 +01:00
Juergen Hoeller 2ee393ae71 Upgrade to Tomcat 9.0.70, Jetty 9.4.50, Netty 4.1.86, Undertow 2.2.22, HtmlUnit 2.67, Mockito 4.9, AssertJ 3.23.1, Checkstyle 10.5 2022-12-23 15:52:09 +01:00
Juergen Hoeller 0815d29e45 Defensive check for null returned from createConnection()
Closes gh-29706
2022-12-23 15:51:37 +01:00
Brian Clozel 777f01d786 Fix path within mapping when pattern contains ".*"
Prior to this commit, extracting the path within handler mapping would
result in "" if the matching path element would be a Regex and contain
".*". This could cause issues with resource handling if the handler
mapping pattern was similar to `"/folder/file.*.extension"`.

This commit introduces a new `isLiteral()` method in the `PathElement`
abstract class that expresses whether the path element can be compared
as a String for path matching or if it requires a more elaborate
matching process.

Using this method for extracting the path within handler mapping avoids
relying on wildcard count or other properties.

See gh-29712
Fixes gh-29716
2022-12-19 10:53:02 +01:00
Sam Brannen f8fea013fc Update Jakarta Mail info in ref docs
Closes gh-29708
2022-12-17 14:18:42 +01:00
Sam Brannen 916539178b Improve documentation for literals in SpEL expressions
Closes gh-29701
2022-12-16 14:51:48 +01:00
Sam Brannen fdf3bcc9d9 Remove obsolete AttributeMethods.hasOnlyValueAttribute() method
See gh-29685
2022-12-13 15:52:01 +01:00
Sam Brannen 5ddc984192 Support repeatable annotation containers with multiple attributes
Prior to this commit, there was a bug in the implementation of
StandardRepeatableContainers.computeRepeatedAnnotationsMethod() which
has existed since Spring Framework 5.2 (when
StandardRepeatableContainers was introduced). Specifically,
StandardRepeatableContainers ignored any repeatable container
annotation if it declared attributes other than `value()`. However,
Java permits any number of attributes in a repeatable container
annotation.

In addition, the changes made in conjunction with gh-20279 made the bug
in StandardRepeatableContainers apparent when using the
getMergedRepeatableAnnotations() or findMergedRepeatableAnnotations()
method in AnnotatedElementUtils, resulting in regressions for the
behavior of those two methods.

This commit fixes the regressions and bug by altering the logic in
StandardRepeatableContainers.computeRepeatedAnnotationsMethod() so that
it explicitly looks for the `value()` method and ignores any other
methods declared in a repeatable container annotation candidate.

See gh-29685
Closes gh-29686
2022-12-13 15:47:13 +01:00
Sam Brannen b2ce54e7f1 Revise RepeatableContainersTests 2022-12-13 15:47:13 +01:00
Juergen Hoeller 937ab5f4b2 Polishing (aligned with main) 2022-12-13 12:06:09 +01:00
Juergen Hoeller 064c618050 Drop SQLExceptionSubclassFactory and unify SQLStateSQLExceptionTranslator tests 2022-12-13 12:05:35 +01:00
Juergen Hoeller 8c80ec1138 Avoid NPE on BeanDescriptor access with SimpleBeanInfoFactory
Closes gh-29681

(cherry picked from commit d74191427e)
2022-12-13 11:43:43 +01:00
rstoyanchev 912fa7602a Improve invalid Content-Type handling in WebFlux
Closes gh-29565
2022-12-09 11:53:16 +00:00
rstoyanchev 525fc7a27e Optimize object creation PartialMatchHelper
Closes gh-29667
2022-12-09 11:27:03 +00:00
Sam Brannen 72285034ba Support arrays in AST string representations of SpEL expressions
Prior to this commit, SpEL's ConstructorReference did not provide
support for arrays when generating a string representation of the
internal AST. For example, 'new String[3]' was represented as 'new
String()' instead of 'new String[3]'.

This commit introduces support for standard array construction and array
construction with initializers in ConstructorReference's toStringAST()
implementation.

Closes gh-29666
2022-12-08 23:23:01 -05:00
Sam Brannen 933474000b Polishing 2022-12-08 18:45:02 -05:00
Sam Brannen 4e8aebcde7 Fix SpEL support for quotes within String literals
Prior to this commit, there were two bugs in the support for quotes
within String literals in SpEL expressions.

- Two double quotes ("") or two single quotes ('') were always replaced
  with one double quote or one single quote, respectively, regardless
  of which quote character was used to enclose the original String
  literal. This resulted in the loss of one of the double quotes when
  the String literal was enclosed in single quotes, and vice versa. For
  example, 'x "" y' became 'x " y'.

- A single quote which was properly escaped in a String literal
  enclosed within single quotes was not escaped in the AST string
  representation of the expression. For example, 'x '' y' became 'x ' y'.

This commit fixes both of these related issues in StringLiteral and
overhauls the structure of ParsingTests.

Closes gh-29604
Closes gh-28356
2022-12-07 16:03:49 -05:00
Sam Brannen dff5b1ff8e Polishing 2022-12-07 15:59:08 -05:00
Sam Brannen fa0a4a0a46 Improve Javadoc for SqlLobValue 2022-12-07 15:58:56 -05:00
Sam Brannen 41a6b7ec20 Update copyright headers for source code changed since August 2022
The changes in this commit were performed using the newly introduced
update_copyright_headers.sh script.
2022-12-03 17:23:21 -05:00
Sam Brannen cfaa4ba89c Apply update_copyright_headers.sh to staged files as well 2022-12-03 17:23:21 -05:00
Sam Brannen 598e9d586b Introduce update_copyright_headers.sh shell script
In order to automate maintenance of copyright headers in our source
code (especially when merging PRs from external contributors), this
commit introduces an update_copyright_headers.sh script (tested on
mac OS) that will update the copyright headers of all Java, Kotlin,
and Groovy source files that have been added or modified this year
(or at least as far back as the git log history supports it).

For example, running this script currently outputs the following.

Updating copyright headers in Java, Kotlin, and Groovy source code for year 2022
warning: log for 'main' only goes back to Tue, 16 Aug 2022 16:24:55 +0200
2022-12-03 17:23:21 -05:00
Sam Brannen 750a8b359f Stop using Mockito to spy() on JDK I/O streams
When running on JDK 16+, we are not able to spy() on JDK types. To
address this, this commit stops using Mockito to spy on JDK I/O streams
(such as ByteArrayInputStream and ByteArrayOutputStream).
2022-12-03 17:23:21 -05:00
Sam Brannen c838bcff3a Fix/Disable JMX & JNDI tests on JDK 16+ 2022-12-03 17:23:21 -05:00
Sam Brannen 109b00d24d Avoid use of deprecated Character/Double constructors in tests 2022-12-03 17:23:21 -05:00
Sam Brannen b6abf45a56 Introduce @Suite classes for individual modules 2022-12-03 16:32:27 -05:00
Juergen Hoeller 2d94b43faa Polishing 2022-12-02 11:15:03 +01:00
Sam Brannen f7f73439ab Modify MBeanTestUtils to work on JDK 17+ 2022-11-30 14:55:15 +01:00
Sam Brannen 5e55558a93 Fix SockJsServiceTests
See gh-29594
2022-11-28 17:12:08 +01:00
Sam Brannen eee50d3b8a Polishing 2022-11-28 17:06:22 +01:00
Sam Brannen 92af390ed2 Polish contribution
See gh-29594
2022-11-28 17:02:53 +01:00
Aashay Chapatwala 332d2a36bb Add title to SockJS iFrames for accessibility compliance
Closes gh-29594
2022-11-28 17:02:01 +01:00
Brian Clozel 92b2b828f5 Fix Java 17 test CI and remove Java 11 variant
This commit fixes configuration and runtime issues with the Java 17 test
CI variant and removes the Java 11 one, now that Spring Framework 5.3.x
is in maintenance mode.
2022-11-28 10:17:56 +01:00
Juergen Hoeller a11222f681 Polishing 2022-11-25 17:58:59 +01:00
Juergen Hoeller 7de8d81932 Explicit documentation notes on standard Java reflection
See gh-29531
2022-11-24 14:25:43 +01:00
Juergen Hoeller 0e33537d9d Consistent use of DefaultParameterNameResolver in tests 2022-11-23 11:17:58 +01:00
Sébastien Deleuze 00da70e26b Fix some typos in Kotlin WebClient example code
Closes gh-29542
2022-11-22 08:35:50 +01:00
Sam Brannen 72b44cebea Catch Error for SourceHttpMessageConverter in WebMvcConfigurationSupport
Prior to this commit, the addDefaultHttpMessageConverters() method in
WebMvcConfigurationSupport caught Throwable for SourceHttpMessageConverter
instantiation; whereas, the rest of the code base correctly catches Error
for SourceHttpMessageConverter instantiation (to handle errors such as
NoClassDefFoundError).

Throwable should not be caught since it can mask other categories of
failures (such as configuration errors).

This commit therefore switches to catching Error for SourceHttpMessageConverter
instantiation in WebMvcConfigurationSupport.

Closes gh-29537
2022-11-21 17:37:22 +01:00
Marten Deinum 1ee3777ac7 Fix link to Bean Utils Light Library in BeanUtils Javadoc
The URL for the BULL library has changed (not sure when, probably way back).

This updates it to the correct location.

Closes gh-29534, gh-29536
2022-11-21 17:25:50 +01:00
divcon cad6e8756d Fix link to WebFlux section in reference manual
Closes gh-29526
2022-11-19 14:51:03 +01:00
Sam Brannen c7bd685c54 Polishing 2022-11-19 14:49:26 +01:00
Sam Brannen 4f472d2ac4 Remove TODOs in WebFlux ref docs 2022-11-19 14:49:07 +01:00
Stephane Nicoll a18842b72b Fix link to WebFlux section
Closes gh-29517
2022-11-18 10:48:05 +01:00
Spring Builds a41f97bc2b Next development version (v5.3.25-SNAPSHOT) 2022-11-16 08:07:01 +00:00
274 changed files with 3841 additions and 2699 deletions
+1
View File
@@ -28,6 +28,7 @@ buildSrc/build
/integration-tests/build
/src/asciidoc/build
target/
/target/
# Eclipse artifacts, including WTP generated manifests
.classpath
+1 -1
View File
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=8.0.352-librca
java=8.0.362-librca
+16 -16
View File
@@ -28,18 +28,18 @@ configure(allprojects) { project ->
dependencyManagement {
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.7"
mavenBom "io.netty:netty-bom:4.1.85.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.25"
mavenBom "io.netty:netty-bom:4.1.90.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.30"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
mavenBom "io.rsocket:rsocket-bom:1.1.3"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.49.v20220914"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.51.v20230217"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.5.32"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2"
mavenBom "org.jetbrains.kotlinx:kotlinx-serialization-bom:1.2.2"
mavenBom "org.junit:junit-bom:5.8.2"
}
dependencies {
dependencySet(group: 'org.apache.logging.log4j', version: '2.19.0') {
dependencySet(group: 'org.apache.logging.log4j', version: '2.20.0') {
entry 'log4j-api'
entry 'log4j-core'
entry 'log4j-jul'
@@ -92,7 +92,7 @@ configure(allprojects) { project ->
entry 'jibx-run'
}
dependency "org.ogce:xpp3:1.1.6"
dependency "org.yaml:snakeyaml:1.30"
dependency "org.yaml:snakeyaml:1.33"
dependency "com.h2database:h2:2.1.214"
dependency "com.github.ben-manes.caffeine:caffeine:2.9.3"
@@ -128,18 +128,18 @@ configure(allprojects) { project ->
dependency "org.webjars:webjars-locator-core:0.48"
dependency "org.webjars:underscorejs:1.8.3"
dependencySet(group: 'org.apache.tomcat', version: '9.0.68') {
dependencySet(group: 'org.apache.tomcat', version: '9.0.73') {
entry 'tomcat-util'
entry('tomcat-websocket') {
exclude group: "org.apache.tomcat", name: "tomcat-servlet-api"
exclude group: "org.apache.tomcat", name: "tomcat-websocket-api"
}
}
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.68') {
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.73') {
entry 'tomcat-embed-core'
entry 'tomcat-embed-websocket'
}
dependencySet(group: 'io.undertow', version: '2.2.21.Final') {
dependencySet(group: 'io.undertow', version: '2.2.23.Final') {
entry 'undertow-core'
entry('undertow-servlet') {
exclude group: "org.jboss.spec.javax.servlet", name: "jboss-servlet-api_4.0_spec"
@@ -191,14 +191,14 @@ configure(allprojects) { project ->
dependency "org.junit.support:testng-engine:1.0.4"
dependency "org.hamcrest:hamcrest:2.2"
dependency "org.awaitility:awaitility:3.1.6"
dependency "org.assertj:assertj-core:3.23.0"
dependency "org.assertj:assertj-core:3.24.2"
dependencySet(group: 'org.xmlunit', version: '2.9.0') {
entry 'xmlunit-assertj'
entry('xmlunit-matchers') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
}
dependencySet(group: 'org.mockito', version: '4.8.1') {
dependencySet(group: 'org.mockito', version: '4.9.0') { // spring-beans tests fail with 4.10+
entry('mockito-core') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
@@ -206,10 +206,10 @@ configure(allprojects) { project ->
}
dependency "io.mockk:mockk:1.12.1"
dependency("net.sourceforge.htmlunit:htmlunit:2.66.0") {
dependency("net.sourceforge.htmlunit:htmlunit:2.70.0") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:htmlunit-driver:2.66.0") {
dependency("org.seleniumhq.selenium:htmlunit-driver:2.70.0") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
@@ -340,7 +340,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "10.4"
toolVersion = "10.9.1"
configDirectory.set(rootProject.file("src/checkstyle"))
}
@@ -377,9 +377,9 @@ configure([rootProject] + javaProjects) { project ->
"https://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/",
"https://www.ehcache.org/apidocs/2.10.4/",
"https://www.quartz-scheduler.org/api/2.3.0/",
"https://fasterxml.github.io/jackson-core/javadoc/2.10/",
"https://fasterxml.github.io/jackson-databind/javadoc/2.10/",
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.10/",
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/2.12.7/",
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/2.12.7/",
"https://www.javadoc.io/doc/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.12.7/",
"https://hc.apache.org/httpcomponents-client-5.1.x/current/httpclient5/apidocs/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.2/",
-1
View File
@@ -5,7 +5,6 @@ ADD get-jdk-url.sh /get-jdk-url.sh
RUN ./setup.sh java8
ENV JAVA_HOME /opt/openjdk/java8
ENV JDK11 /opt/openjdk/java11
ENV JDK17 /opt/openjdk/java17
ENV PATH $JAVA_HOME/bin:$PATH
-6
View File
@@ -5,15 +5,9 @@ case "$1" in
java8)
echo "https://github.com/bell-sw/Liberica/releases/download/8u345+1/bellsoft-jdk8u345+1-linux-amd64.tar.gz"
;;
java11)
echo "https://github.com/bell-sw/Liberica/releases/download/11.0.16+8/bellsoft-jdk11.0.16+8-linux-amd64.tar.gz"
;;
java17)
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.4+8/bellsoft-jdk17.0.4+8-linux-amd64.tar.gz"
;;
java18)
echo "https://github.com/bell-sw/Liberica/releases/download/18.0.2+10/bellsoft-jdk18.0.2+10-linux-amd64.tar.gz"
;;
*)
echo $"Unknown java version"
exit 1
+1 -1
View File
@@ -20,7 +20,7 @@ curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/c
mkdir -p /opt/openjdk
pushd /opt/openjdk > /dev/null
for jdk in java8 java11 java17
for jdk in java8 java17
do
JDK_URL=$( /get-jdk-url.sh $jdk )
mkdir $jdk
-3
View File
@@ -8,7 +8,4 @@ milestone: "5.3.x"
build-name: "spring-framework"
pipeline-name: "spring-framework"
concourse-url: "https://ci.spring.io"
registry-mirror-host: docker.repo.spring.io
registry-mirror-username: ((artifactory-username))
registry-mirror-password: ((artifactory-password))
task-timeout: 1h00m
+13 -47
View File
@@ -23,11 +23,6 @@ anchors:
docker-resource-source: &docker-resource-source
username: ((docker-hub-username))
password: ((docker-hub-password))
tag: ((milestone))
registry-mirror-vars: &registry-mirror-vars
registry-mirror-host: ((registry-mirror-host))
registry-mirror-username: ((registry-mirror-username))
registry-mirror-password: ((registry-mirror-password))
slack-fail-params: &slack-fail-params
text: >
:concourse-failed: <https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}|${BUILD_PIPELINE_NAME} ${BUILD_JOB_NAME} failed!>
@@ -48,31 +43,37 @@ resource_types:
- name: registry-image
type: registry-image
source:
<<: *docker-resource-source
repository: concourse/registry-image-resource
tag: 1.5.0
tag: 1.7.1
- name: artifactory-resource
type: registry-image
source:
<<: *docker-resource-source
repository: springio/artifactory-resource
tag: 0.0.17
tag: 0.0.18
- name: github-release
type: registry-image
source:
<<: *docker-resource-source
repository: concourse/github-release-resource
tag: 1.5.5
tag: 1.8.0
- name: github-status-resource
type: registry-image
source:
<<: *docker-resource-source
repository: dpb587/github-status-resource
tag: master
- name: pull-request
type: registry-image
source:
<<: *docker-resource-source
repository: teliaoss/github-pr-resource
tag: v0.23.0
- name: slack-notification
type: registry-image
source:
<<: *docker-resource-source
repository: cfcommunity/slack-notification-resource
tag: latest
resources:
@@ -101,6 +102,7 @@ resources:
source:
<<: *docker-resource-source
repository: ((docker-hub-organization))/spring-framework-ci
tag: ((milestone))
- name: artifactory-repo
type: artifactory-resource
icon: package-variant
@@ -125,14 +127,6 @@ resources:
access_token: ((github-ci-status-token))
branch: ((branch))
context: build
- name: repo-status-jdk11-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: jdk11-build
- name: repo-status-jdk17-build
type: github-status-resource
icon: eye-check-outline
@@ -176,7 +170,7 @@ jobs:
image: ci-image
vars:
ci-image-name: ci-image
<<: *registry-mirror-vars
<<: *docker-resource-source
- put: ci-image
params:
image: ci-image/image.tar
@@ -237,34 +231,6 @@ jobs:
"zip.type": "schema"
get_params:
threads: 8
- name: jdk11-build
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
- get: every-morning
trigger: true
- put: repo-status-jdk11-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: check-project
image: ci-image
file: git-repo/ci/tasks/check-project.yml
privileged: true
timeout: ((task-timeout))
params:
TEST_TOOLCHAIN: 11
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-jdk11-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-jdk11-build
params: { state: "success", commit: "git-repo" }
- name: jdk17-build
serial: true
public: true
@@ -282,7 +248,7 @@ jobs:
privileged: true
timeout: ((task-timeout))
params:
TEST_TOOLCHAIN: 15
TEST_TOOLCHAIN: 17
<<: *build-project-task-params
on_failure:
do:
@@ -471,7 +437,7 @@ jobs:
groups:
- name: "builds"
jobs: ["build", "jdk11-build", "jdk17-build"]
jobs: ["build", "jdk17-build"]
- name: "releases"
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
- name: "ci-images"
+1 -1
View File
@@ -4,6 +4,6 @@ set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK11,JDK15 \
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17 \
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check
popd > /dev/null
+3 -5
View File
@@ -4,11 +4,9 @@ image_resource:
type: registry-image
source:
repository: concourse/oci-build-task
tag: 0.9.1
registry_mirror:
host: ((registry-mirror-host))
username: ((registry-mirror-username))
password: ((registry-mirror-password))
tag: 0.10.0
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
- name: ci-images-git-repo
outputs:
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.24-SNAPSHOT
version=5.3.26
org.gradle.jvmargs=-Xmx2048m
org.gradle.caching=true
org.gradle.parallel=true
+4 -15
View File
@@ -81,6 +81,10 @@ plugins.withType(JavaPlugin) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = testLanguageVersion
}
if(testLanguageVersion == JavaLanguageVersion.of(17)) {
jvmArgs(["--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.util=ALL-UNNAMED"])
}
}
}
}
@@ -130,21 +134,6 @@ pluginManager.withPlugin("kotlin") {
}
}
}
if (testToolchainConfigured()) {
def testLanguageVersion = testToolchainLanguageVersion()
def compiler = javaToolchains.compilerFor {
languageVersion = testLanguageVersion
}
// See https://kotlinlang.org/docs/gradle.html#attributes-specific-for-jvm
def javaVersion = testLanguageVersion.toString() == '8' ? '1.8' : testLanguageVersion.toString()
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
jdkHome = compiler.get().metadata.installationPath.asFile.absolutePath
}
}
}
}
// Configure the JMH plugin to use the toolchain for generating and running JMH bytecode
+2 -2
View File
@@ -7,8 +7,8 @@ pluginManagement {
}
plugins {
id "com.gradle.enterprise" version "3.11.1"
id "io.spring.ge.conventions" version "0.0.11"
id "com.gradle.enterprise" version "3.12.1"
id "io.spring.ge.conventions" version "0.0.13"
}
include "spring-aop"
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,10 +23,6 @@ import org.springframework.lang.Nullable;
* Subinterface of {@link org.springframework.aop.aspectj.AspectInstanceFactory}
* that returns {@link AspectMetadata} associated with AspectJ-annotated classes.
*
* <p>Ideally, AspectInstanceFactory would include this method itself, but because
* AspectMetadata uses Java-5-only {@link org.aspectj.lang.reflect.AjType},
* we need to split out this subinterface.
*
* @author Rod Johnson
* @since 2.0
* @see AspectMetadata
@@ -35,13 +31,13 @@ import org.springframework.lang.Nullable;
public interface MetadataAwareAspectInstanceFactory extends AspectInstanceFactory {
/**
* Return the AspectJ AspectMetadata for this factory's aspect.
* Get the AspectJ AspectMetadata for this factory's aspect.
* @return the aspect metadata
*/
AspectMetadata getAspectMetadata();
/**
* Return the best possible creation mutex for this factory.
* Get the best possible creation mutex for this factory.
* @return the mutex object (may be {@code null} for no mutex to use)
* @since 4.3
*/
@@ -0,0 +1,10 @@
/**
* Various {@link org.springframework.aop.framework.autoproxy.TargetSourceCreator}
* implementations for use with Spring's AOP auto-proxying support.
*/
@NonNullApi
@NonNullFields
package org.springframework.aop.framework.autoproxy.target;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;
@@ -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.
@@ -62,7 +62,7 @@ import org.springframework.util.StringUtils;
* <p>Mainly for internal use within the framework, but to some degree also
* useful for application classes. Consider
* <a href="https://commons.apache.org/proper/commons-beanutils/">Apache Commons BeanUtils</a>,
* <a href="https://hotelsdotcom.github.io/bull/">BULL - Bean Utils Light Library</a>,
* <a href="https://github.com/ExpediaGroup/bull">BULL - Bean Utils Light Library</a>,
* or similar third-party frameworks for more comprehensive bean utilities.
*
* @author Rod Johnson
@@ -247,7 +247,8 @@ public abstract class BeanUtils {
// A single public constructor
return (Constructor<T>) ctors[0];
}
else if (ctors.length == 0){
else if (ctors.length == 0) {
// No public constructors -> check non-public
ctors = clazz.getDeclaredConstructors();
if (ctors.length == 1) {
// A single non-public constructor, e.g. from a non-public record type
@@ -16,6 +16,7 @@
package org.springframework.beans;
import java.beans.BeanDescriptor;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
@@ -51,6 +52,10 @@ public class SimpleBeanInfoFactory implements BeanInfoFactory, Ordered {
PropertyDescriptorUtils.determineBasicProperties(beanClass);
return new SimpleBeanInfo() {
@Override
public BeanDescriptor getBeanDescriptor() {
return new BeanDescriptor(beanClass);
}
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
return pds.toArray(PropertyDescriptorUtils.EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
@@ -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.
@@ -25,6 +25,7 @@ import java.util.Set;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.DependencyDescriptor;
@@ -240,10 +241,11 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
}
}
if (targetAnnotation == null) {
BeanFactory beanFactory = getBeanFactory();
// Look for matching annotation on the target class
if (getBeanFactory() != null) {
if (beanFactory != null) {
try {
Class<?> beanType = getBeanFactory().getType(bdHolder.getBeanName());
Class<?> beanType = beanFactory.getType(bdHolder.getBeanName());
if (beanType != null) {
targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(beanType), type);
}
@@ -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.
@@ -184,8 +184,9 @@ public abstract class YamlProcessor {
protected Yaml createYaml() {
LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setAllowDuplicateKeys(false);
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(),
new DumperOptions(), loaderOptions);
DumperOptions dumperOptions = new DumperOptions();
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(dumperOptions),
dumperOptions, loaderOptions);
}
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -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.
@@ -214,12 +214,15 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
}
catch (Throwable ex) {
String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
if (logger.isDebugEnabled()) {
logger.warn(msg, ex);
}
else {
logger.warn(msg + ": " + ex);
if (logger.isWarnEnabled()) {
String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
if (logger.isDebugEnabled()) {
// Log at warn level like below but add the exception stacktrace only with debug level
logger.warn(msg, ex);
}
else {
logger.warn(msg + ": " + ex);
}
}
}
}
@@ -240,12 +243,15 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
}
catch (Throwable ex) {
String msg = "Invocation of close method failed on bean with name '" + this.beanName + "'";
if (logger.isDebugEnabled()) {
logger.warn(msg, ex);
}
else {
logger.warn(msg + ": " + ex);
if (logger.isWarnEnabled()) {
String msg = "Invocation of close method failed on bean with name '" + this.beanName + "'";
if (logger.isDebugEnabled()) {
// Log at warn level like below but add the exception stacktrace only with debug level
logger.warn(msg, ex);
}
else {
logger.warn(msg + ": " + ex);
}
}
}
}
@@ -320,18 +326,23 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
}
catch (InvocationTargetException ex) {
String msg = "Custom destroy method '" + this.destroyMethodName + "' on bean with name '" +
this.beanName + "' threw an exception";
if (logger.isDebugEnabled()) {
logger.warn(msg, ex.getTargetException());
}
else {
logger.warn(msg + ": " + ex.getTargetException());
if (logger.isWarnEnabled()) {
String msg = "Custom destroy method '" + this.destroyMethodName + "' on bean with name '" +
this.beanName + "' threw an exception";
if (logger.isDebugEnabled()) {
// Log at warn level like below but add the exception stacktrace only with debug level
logger.warn(msg, ex.getTargetException());
}
else {
logger.warn(msg + ": " + ex.getTargetException());
}
}
}
catch (Throwable ex) {
logger.warn("Failed to invoke custom destroy method '" + this.destroyMethodName +
"' on bean with name '" + this.beanName + "'", ex);
if (logger.isWarnEnabled()) {
logger.warn("Failed to invoke custom destroy method '" + this.destroyMethodName +
"' on bean with name '" + this.beanName + "'", ex);
}
}
}
@@ -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.
@@ -547,7 +547,8 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
* @see DefaultNamespaceHandlerResolver#DefaultNamespaceHandlerResolver(ClassLoader)
*/
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
ResourceLoader resourceLoader = getResourceLoader();
ClassLoader cl = (resourceLoader != null ? resourceLoader.getClassLoader() : getBeanClassLoader());
return new DefaultNamespaceHandlerResolver(cl);
}
@@ -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.
@@ -77,6 +77,21 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests {
assertThat(accessor.getPropertyValue("aliasedName")).isEqualTo("tom");
}
@Test
void replaceWrappedInstance() {
GetterBean target = new GetterBean();
BeanWrapperImpl accessor = createAccessor(target);
accessor.setPropertyValue("name", "tom");
assertThat(target.getAliasedName()).isEqualTo("tom");
assertThat(accessor.getPropertyValue("aliasedName")).isEqualTo("tom");
target = new GetterBean();
accessor.setWrappedInstance(target);
accessor.setPropertyValue("name", "tom");
assertThat(target.getAliasedName()).isEqualTo("tom");
assertThat(accessor.getPropertyValue("aliasedName")).isEqualTo("tom");
}
@Test
void setValidAndInvalidPropertyValuesShouldContainExceptionDetails() {
TestBean target = new TestBean();
@@ -380,6 +395,7 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests {
}
@SuppressWarnings("try")
public static class ActiveResource implements AutoCloseable {
public ActiveResource getResource() {
@@ -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.
@@ -436,7 +436,6 @@ class DefaultListableBeanFactoryTests {
@Test
void empty() {
ListableBeanFactory lbf = new DefaultListableBeanFactory();
assertThat(lbf.getBeanDefinitionNames() != null).as("No beans defined --> array != null").isTrue();
assertThat(lbf.getBeanDefinitionNames().length == 0).as("No beans defined after no arg constructor").isTrue();
assertThat(lbf.getBeanDefinitionCount() == 0).as("No beans defined after no arg constructor").isTrue();
@@ -778,21 +777,6 @@ class DefaultListableBeanFactoryTests {
assertThat(factory.getType("child")).isEqualTo(DerivedTestBean.class);
}
@Test
void nameAlreadyBound() {
Properties p = new Properties();
p.setProperty("kerry.(class)", TestBean.class.getName());
p.setProperty("kerry.age", "35");
registerBeanDefinitions(p);
try {
registerBeanDefinitions(p);
}
catch (BeanDefinitionStoreException ex) {
assertThat(ex.getBeanName()).isEqualTo("kerry");
// expected
}
}
private void singleTestBean(ListableBeanFactory lbf) {
assertThat(lbf.getBeanDefinitionCount() == 1).as("1 beans defined").isTrue();
String[] names = lbf.getBeanDefinitionNames();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.util.ClassUtils;
@@ -148,7 +148,7 @@ public class QualifierAnnotationAutowireBeanFactoryTests {
lbf.registerBeanDefinition(MARK, person2);
MethodParameter param = new MethodParameter(QualifiedTestBean.class.getDeclaredConstructor(Person.class), 0);
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(param, false);
param.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
param.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
assertThat(param.getParameterName()).isEqualTo("tpb");
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue();
assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isTrue();
@@ -174,9 +174,9 @@ public class QualifierAnnotationAutowireBeanFactoryTests {
new MethodParameter(QualifiedTestBean.class.getDeclaredMethod("autowireNonqualified", Person.class), 0);
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(qualifiedParam, false);
DependencyDescriptor nonqualifiedDescriptor = new DependencyDescriptor(nonqualifiedParam, false);
qualifiedParam.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
qualifiedParam.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
assertThat(qualifiedParam.getParameterName()).isEqualTo("tpb");
nonqualifiedParam.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
nonqualifiedParam.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
assertThat(nonqualifiedParam.getParameterName()).isEqualTo("tpb");
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue();
assertThat(lbf.isAutowireCandidate(JUERGEN, nonqualifiedDescriptor)).isTrue();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -502,7 +502,7 @@ class CustomEditorTests {
CharBean cb = new CharBean();
BeanWrapper bw = new BeanWrapperImpl(cb);
bw.setPropertyValue("myChar", new Character('c'));
bw.setPropertyValue("myChar", 'c');
assertThat(cb.getMyChar()).isEqualTo('c');
bw.setPropertyValue("myChar", "c");
@@ -526,16 +526,16 @@ class CustomEditorTests {
bw.registerCustomEditor(Character.class, new CharacterEditor(true));
bw.setPropertyValue("myCharacter", 'c');
assertThat(cb.getMyCharacter()).isEqualTo(Character.valueOf('c'));
assertThat(cb.getMyCharacter()).isEqualTo('c');
bw.setPropertyValue("myCharacter", "c");
assertThat(cb.getMyCharacter()).isEqualTo(Character.valueOf('c'));
assertThat(cb.getMyCharacter()).isEqualTo('c');
bw.setPropertyValue("myCharacter", "\u0041");
assertThat(cb.getMyCharacter()).isEqualTo(Character.valueOf('A'));
assertThat(cb.getMyCharacter()).isEqualTo('A');
bw.setPropertyValue("myCharacter", " ");
assertThat(cb.getMyCharacter()).isEqualTo(Character.valueOf(' '));
assertThat(cb.getMyCharacter()).isEqualTo(' ');
bw.setPropertyValue("myCharacter", "");
assertThat(cb.getMyCharacter()).isNull();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -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.
@@ -51,6 +51,7 @@ import org.springframework.util.ObjectUtils;
* @author Juergen Hoeller
* @author Stephane Nicoll
* @author Sam Brannen
* @author Brian Clozel
* @since 4.3
* @see CaffeineCache
*/
@@ -188,8 +189,13 @@ public class CaffeineCacheManager implements CacheManager {
@Override
@Nullable
public Cache getCache(String name) {
return this.cacheMap.computeIfAbsent(name, cacheName ->
this.dynamic ? createCaffeineCache(cacheName) : null);
if (this.dynamic) {
Cache cache = this.cacheMap.get(name);
return (cache != null) ? cache : this.cacheMap.computeIfAbsent(name, this::createCaffeineCache);
}
else {
return this.cacheMap.get(name);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 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.
@@ -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.
@@ -132,10 +132,10 @@ public class JavaMailSenderImpl implements JavaMailSender {
}
/**
* Allow {code Map} access to the JavaMail properties of this sender,
* Allow {@code Map} access to the JavaMail properties of this sender,
* with the option to add or override specific entries.
* <p>Useful for specifying entries directly, for example via
* {code javaMailProperties[mail.smtp.auth]}.
* {@code javaMailProperties[mail.smtp.auth]}.
*/
public Properties getJavaMailProperties() {
return this.javaMailProperties;
@@ -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.
@@ -729,9 +729,11 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/**
* Modify the application context's internal bean factory after its standard
* initialization. All bean definitions will have been loaded, but no beans
* will have been instantiated yet. This allows for registering special
* BeanPostProcessors etc in certain ApplicationContext implementations.
* initialization. The initial definition resources will have been loaded but no
* post-processors will have run and no derived bean definitions will have been
* registered, and most importantly, no beans will have been instantiated yet.
* <p>This template method allows for registering special BeanPostProcessors
* etc in certain AbstractApplicationContext subclasses.
* @param beanFactory the bean factory used by the application context
*/
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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,7 +20,6 @@ import java.beans.PropertyDescriptor;
import javax.management.DynamicMBean;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
@@ -32,6 +31,7 @@ import org.springframework.beans.BeanWrapperImpl;
import org.springframework.jmx.IJmxTestBean;
import org.springframework.jmx.JmxTestBean;
import org.springframework.jmx.export.TestDynamicMBean;
import org.springframework.util.MBeanTestUtils;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Rob Harrop
* @author Juergen Hoeller
* @author Sam Brannen
*/
class JmxUtilsTests {
@@ -131,10 +132,11 @@ class JmxUtilsTests {
MBeanServer server = null;
try {
server = JmxUtils.locateMBeanServer();
assertThat(server).isNotNull();
}
finally {
if (server != null) {
MBeanServerFactory.releaseMBeanServer(server);
MBeanTestUtils.releaseMBeanServer(server);
}
}
}
@@ -21,21 +21,25 @@ import java.lang.reflect.Field;
import javax.naming.spi.NamingManager;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledForJreRange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.condition.JRE.JAVA_16;
/**
* Tests for {@link JndiLocatorDelegate}.
*
* @author Phillip Webb
* @author Juergen Hoeller
* @author Sam Brannen
*/
public class JndiLocatorDelegateTests {
@DisabledForJreRange(
min = JAVA_16,
disabledReason = "Cannot use reflection to set private static field in javax.naming.spi.NamingManager")
class JndiLocatorDelegateTests {
@Test
public void isDefaultJndiEnvironmentAvailableFalse() throws Exception {
void isDefaultJndiEnvironmentAvailableFalse() throws Exception {
Field builderField = NamingManager.class.getDeclaredField("initctx_factory_builder");
builderField.setAccessible(true);
Object oldBuilder = builderField.get(null);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,37 +18,59 @@ package org.springframework.util;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.util.EnumSet;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import org.junit.jupiter.api.condition.JRE;
/**
* Utilities for MBean tests.
*
* @author Phillip Webb
* @author Sam Brannen
*/
public class MBeanTestUtils {
/**
* Resets MBeanServerFactory and ManagementFactory to a known consistent state.
* <p>This involves releasing all currently registered MBeanServers and resetting
* the platformMBeanServer to null.
* Reset the {@link MBeanServerFactory} to a known consistent state. This involves
* {@linkplain #releaseMBeanServer(MBeanServer) releasing} all currently registered
* MBeanServers.
* <p>On JDK 8 - JDK 16, this method also resets the platformMBeanServer field
* in {@link ManagementFactory} to {@code null}.
*/
public static synchronized void resetMBeanServers() throws Exception {
for (MBeanServer server : MBeanServerFactory.findMBeanServer(null)) {
try {
MBeanServerFactory.releaseMBeanServer(server);
}
catch (IllegalArgumentException ex) {
if (!ex.getMessage().contains("not in list")) {
throw ex;
}
}
releaseMBeanServer(server);
}
Field field = ManagementFactory.class.getDeclaredField("platformMBeanServer");
field.setAccessible(true);
field.set(null, null);
if (!isCurrentJreWithinRange(JRE.JAVA_16, JRE.OTHER)) {
Field field = ManagementFactory.class.getDeclaredField("platformMBeanServer");
field.setAccessible(true);
field.set(null, null);
}
}
/**
* Attempt to release the supplied {@link MBeanServer}.
* <p>Ignores any {@link IllegalArgumentException} thrown by
* {@link MBeanServerFactory#releaseMBeanServer(MBeanServer)} whose error
* message contains the text "not in list".
*/
public static void releaseMBeanServer(MBeanServer server) {
try {
MBeanServerFactory.releaseMBeanServer(server);
}
catch (IllegalArgumentException ex) {
if (!ex.getMessage().contains("not in list")) {
throw ex;
}
}
}
static boolean isCurrentJreWithinRange(JRE min, JRE max) {
return EnumSet.range(min, max).contains(JRE.currentVersion());
}
}
@@ -82,6 +82,17 @@ public abstract class AnnotationVisitor {
this.av = annotationVisitor;
}
/**
* The annotation visitor to which this visitor must delegate method calls. May be {@literal
* null}.
*
* @return the annotation visitor to which this visitor must delegate method calls, or {@literal
* null}.
*/
public AnnotationVisitor getDelegate() {
return av;
}
/**
* Visits a primitive value of the annotation.
*
@@ -194,7 +194,7 @@ public class ClassReader {
this.b = classFileBuffer;
// Check the class' major_version. This field is after the magic and minor_version fields, which
// use 4 and 2 bytes respectively.
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V19) {
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V21) {
throw new IllegalArgumentException(
"Unsupported class file major version " + readShort(classFileOffset + 6));
}
@@ -308,6 +308,7 @@ public class ClassReader {
* @return the content of the given input stream.
* @throws IOException if a problem occurs during reading.
*/
@SuppressWarnings("PMD.UseTryWithResources")
private static byte[] readStream(final InputStream inputStream, final boolean close)
throws IOException {
if (inputStream == null) {
@@ -376,7 +377,7 @@ public class ClassReader {
}
/**
* Returns the internal of name of the super class (see {@link Type#getInternalName()}). For
* Returns the internal name of the super class (see {@link Type#getInternalName()}). For
* interfaces, the super class is {@link Object}.
*
* @return the internal name of the super class, or {@literal null} for {@link Object} class.
@@ -42,7 +42,8 @@ public final class ClassTooLargeException extends IndexOutOfBoundsException {
/**
* Constructs a new {@link ClassTooLargeException}.
*
* @param className the internal name of the class.
* @param className the internal name of the class (see {@link
* org.objectweb.asm.Type#getInternalName()}).
* @param constantPoolCount the number of constant pool items of the class.
*/
public ClassTooLargeException(final String className, final int constantPoolCount) {
@@ -52,7 +53,7 @@ public final class ClassTooLargeException extends IndexOutOfBoundsException {
}
/**
* Returns the internal name of the class.
* Returns the internal name of the class (see {@link org.objectweb.asm.Type#getInternalName()}).
*
* @return the internal name of the class.
*/
@@ -81,6 +81,15 @@ public abstract class ClassVisitor {
this.cv = classVisitor;
}
/**
* The class visitor to which this visitor must delegate method calls. May be {@literal null}.
*
* @return the class visitor to which this visitor must delegate method calls, or {@literal null}.
*/
public ClassVisitor getDelegate() {
return cv;
}
/**
* Visits the header of the class.
*
@@ -155,7 +164,8 @@ public abstract class ClassVisitor {
* implicitly its own nest, so it's invalid to call this method with the visited class name as
* argument.
*
* @param nestHost the internal name of the host class of the nest.
* @param nestHost the internal name of the host class of the nest (see {@link
* Type#getInternalName()}).
*/
public void visitNestHost(final String nestHost) {
if (api < Opcodes.ASM7) {
@@ -167,14 +177,19 @@ public abstract class ClassVisitor {
}
/**
* Visits the enclosing class of the class. This method must be called only if the class has an
* enclosing class.
* Visits the enclosing class of the class. This method must be called only if this class is a
* local or anonymous class. See the JVMS 4.7.7 section for more details.
*
* @param owner internal name of the enclosing class of the class.
* @param owner internal name of the enclosing class of the class (see {@link
* Type#getInternalName()}).
* @param name the name of the method that contains the class, or {@literal null} if the class is
* not enclosed in a method of its enclosing class.
* not enclosed in a method or constructor of its enclosing class (e.g. if it is enclosed in
* an instance initializer, static initializer, instance variable initializer, or class
* variable initializer).
* @param descriptor the descriptor of the method that contains the class, or {@literal null} if
* the class is not enclosed in a method of its enclosing class.
* the class is not enclosed in a method or constructor of its enclosing class (e.g. if it is
* enclosed in an instance initializer, static initializer, instance variable initializer, or
* class variable initializer).
*/
public void visitOuterClass(final String owner, final String name, final String descriptor) {
if (cv != null) {
@@ -241,7 +256,7 @@ public abstract class ClassVisitor {
* the visited class is the host of a nest. A nest host is implicitly a member of its own nest, so
* it's invalid to call this method with the visited class name as argument.
*
* @param nestMember the internal name of a nest member.
* @param nestMember the internal name of a nest member (see {@link Type#getInternalName()}).
*/
public void visitNestMember(final String nestMember) {
if (api < Opcodes.ASM7) {
@@ -256,7 +271,8 @@ public abstract class ClassVisitor {
* Visits a permitted subclasses. A permitted subclass is one of the allowed subclasses of the
* current class.
*
* @param permittedSubclass the internal name of a permitted subclass.
* @param permittedSubclass the internal name of a permitted subclass (see {@link
* Type#getInternalName()}).
*/
public void visitPermittedSubclass(final String permittedSubclass) {
if (api < Opcodes.ASM9) {
@@ -269,15 +285,18 @@ public abstract class ClassVisitor {
/**
* Visits information about an inner class. This inner class is not necessarily a member of the
* class being visited.
* class being visited. More precisely, every class or interface C which is referenced by this
* class and which is not a package member must be visited with this method. This class must
* reference its nested class or interface members, and its enclosing class, if any. See the JVMS
* 4.7.6 section for more details.
*
* @param name the internal name of an inner class (see {@link Type#getInternalName()}).
* @param outerName the internal name of the class to which the inner class belongs (see {@link
* Type#getInternalName()}). May be {@literal null} for not member classes.
* @param innerName the (simple) name of the inner class inside its enclosing class. May be
* {@literal null} for anonymous inner classes.
* @param access the access flags of the inner class as originally declared in the enclosing
* class.
* @param name the internal name of C (see {@link Type#getInternalName()}).
* @param outerName the internal name of the class or interface C is a member of (see {@link
* Type#getInternalName()}). Must be {@literal null} if C is not the member of a class or
* interface (e.g. for local or anonymous classes).
* @param innerName the (simple) name of C. Must be {@literal null} for anonymous inner classes.
* @param access the access flags of C originally declared in the source code from which this
* class was compiled.
*/
public void visitInnerClass(
final String name, final String outerName, final String innerName, final int access) {
@@ -842,7 +842,7 @@ public class ClassWriter extends ClassVisitor {
* constant pool already contains a similar item. <i>This method is intended for {@link Attribute}
* sub classes, and is normally not needed by class generators or adapters.</i>
*
* @param value the internal name of the class.
* @param value the internal name of the class (see {@link Type#getInternalName()}).
* @return the index of a new or already existing class reference item.
*/
public int newClass(final String value) {
@@ -894,7 +894,8 @@ public class ClassWriter extends ClassVisitor {
* Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link
* Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL},
* {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
* @param owner the internal name of the field or method owner class.
* @param owner the internal name of the field or method owner class (see {@link
* Type#getInternalName()}).
* @param name the name of the field or method.
* @param descriptor the descriptor of the field or method.
* @return the index of a new or already existing method type reference item.
@@ -916,7 +917,8 @@ public class ClassWriter extends ClassVisitor {
* Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link
* Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL},
* {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
* @param owner the internal name of the field or method owner class.
* @param owner the internal name of the field or method owner class (see {@link
* Type#getInternalName()}).
* @param name the name of the field or method.
* @param descriptor the descriptor of the field or method.
* @param isInterface true if the owner is an interface.
@@ -978,7 +980,7 @@ public class ClassWriter extends ClassVisitor {
* constant pool already contains a similar item. <i>This method is intended for {@link Attribute}
* sub classes, and is normally not needed by class generators or adapters.</i>
*
* @param owner the internal name of the field's owner class.
* @param owner the internal name of the field's owner class (see {@link Type#getInternalName()}).
* @param name the field's name.
* @param descriptor the field's descriptor.
* @return the index of a new or already existing field reference item.
@@ -992,7 +994,8 @@ public class ClassWriter extends ClassVisitor {
* constant pool already contains a similar item. <i>This method is intended for {@link Attribute}
* sub classes, and is normally not needed by class generators or adapters.</i>
*
* @param owner the internal name of the method's owner class.
* @param owner the internal name of the method's owner class (see {@link
* Type#getInternalName()}).
* @param name the method's name.
* @param descriptor the method's descriptor.
* @param isInterface {@literal true} if {@code owner} is an interface.
@@ -1028,9 +1031,10 @@ public class ClassWriter extends ClassVisitor {
* currently being generated by this ClassWriter, which can of course not be loaded since it is
* under construction.
*
* @param type1 the internal name of a class.
* @param type2 the internal name of another class.
* @return the internal name of the common super class of the two given classes.
* @param type1 the internal name of a class (see {@link Type#getInternalName()}).
* @param type2 the internal name of another class (see {@link Type#getInternalName()}).
* @return the internal name of the common super class of the two given classes (see {@link
* Type#getInternalName()}).
*/
protected String getCommonSuperClass(final String type1, final String type2) {
ClassLoader classLoader = getClassLoader();
@@ -78,6 +78,15 @@ public abstract class FieldVisitor {
this.fv = fieldVisitor;
}
/**
* The field visitor to which this visitor must delegate method calls. May be {@literal null}.
*
* @return the field visitor to which this visitor must delegate method calls, or {@literal null}.
*/
public FieldVisitor getDelegate() {
return fv;
}
/**
* Visits an annotation of the field.
*
@@ -367,11 +367,12 @@ class Frame {
typeValue = REFERENCE_KIND | symbolTable.addType(internalName);
break;
default:
throw new IllegalArgumentException();
throw new IllegalArgumentException(
"Invalid descriptor fragment: " + buffer.substring(elementDescriptorOffset));
}
return ((elementDescriptorOffset - offset) << DIM_SHIFT) | typeValue;
default:
throw new IllegalArgumentException();
throw new IllegalArgumentException("Invalid descriptor: " + buffer.substring(offset));
}
}
@@ -65,7 +65,7 @@ public final class Handle {
* {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link
* Opcodes#H_INVOKEINTERFACE}.
* @param owner the internal name of the class that owns the field or method designated by this
* handle.
* handle (see {@link Type#getInternalName()}).
* @param name the name of the field or method designated by this handle.
* @param descriptor the descriptor of the field or method designated by this handle.
* @deprecated this constructor has been superseded by {@link #Handle(int, String, String, String,
@@ -85,7 +85,7 @@ public final class Handle {
* {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link
* Opcodes#H_INVOKEINTERFACE}.
* @param owner the internal name of the class that owns the field or method designated by this
* handle.
* handle (see {@link Type#getInternalName()}).
* @param name the name of the field or method designated by this handle.
* @param descriptor the descriptor of the field or method designated by this handle.
* @param isInterface whether the owner is an interface or not.
@@ -118,7 +118,8 @@ public final class Handle {
/**
* Returns the internal name of the class that owns the field or method designated by this handle.
*
* @return the internal name of the class that owns the field or method designated by this handle.
* @return the internal name of the class that owns the field or method designated by this handle
* (see {@link Type#getInternalName()}).
*/
public String getOwner() {
return owner;
@@ -44,7 +44,7 @@ public final class MethodTooLargeException extends IndexOutOfBoundsException {
/**
* Constructs a new {@link MethodTooLargeException}.
*
* @param className the internal name of the owner class.
* @param className the internal name of the owner class (see {@link Type#getInternalName()}).
* @param methodName the name of the method.
* @param descriptor the descriptor of the method.
* @param codeSize the size of the method's Code attribute, in bytes.
@@ -64,7 +64,7 @@ public final class MethodTooLargeException extends IndexOutOfBoundsException {
/**
* Returns the internal name of the owner class.
*
* @return the internal name of the owner class.
* @return the internal name of the owner class (see {@link Type#getInternalName()}).
*/
public String getClassName() {
return className;
@@ -94,6 +94,16 @@ public abstract class MethodVisitor {
this.mv = methodVisitor;
}
/**
* The method visitor to which this visitor must delegate method calls. May be {@literal null}.
*
* @return the method visitor to which this visitor must delegate method calls, or {@literal
* null}.
*/
public MethodVisitor getDelegate() {
return mv;
}
// -----------------------------------------------------------------------------------------------
// Parameters, annotations and non standard attributes
// -----------------------------------------------------------------------------------------------
@@ -120,7 +130,7 @@ public abstract class MethodVisitor {
* @return a visitor to the visit the actual default value of this annotation interface method, or
* {@literal null} if this visitor is not interested in visiting this default value. The
* 'name' parameters passed to the methods of this annotation visitor are ignored. Moreover,
* exacly one visit method must be called on this annotation visitor, followed by visitEnd.
* exactly one visit method must be called on this annotation visitor, followed by visitEnd.
*/
public AnnotationVisitor visitAnnotationDefault() {
if (mv != null) {
@@ -273,15 +283,17 @@ public abstract class MethodVisitor {
* @param type the type of this stack map frame. Must be {@link Opcodes#F_NEW} for expanded
* frames, or {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link
* Opcodes#F_SAME} or {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed frames.
* @param numLocal the number of local variables in the visited frame.
* @param numLocal the number of local variables in the visited frame. Long and double values
* count for one variable.
* @param local the local variable types in this frame. This array must not be modified. Primitive
* types are represented by {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link
* Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL} or
* {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented by a single element).
* Reference types are represented by String objects (representing internal names), and
* uninitialized types by Label objects (this label designates the NEW instruction that
* created this uninitialized value).
* @param numStack the number of operand stack elements in the visited frame.
* Reference types are represented by String objects (representing internal names, see {@link
* Type#getInternalName()}), and uninitialized types by Label objects (this label designates
* the NEW instruction that created this uninitialized value).
* @param numStack the number of operand stack elements in the visited frame. Long and double
* values count for one stack element.
* @param stack the operand stack types in this frame. This array must not be modified. Its
* content has the same format as the "local" array.
* @throws IllegalStateException if a frame is visited just after another one, without any
@@ -360,7 +372,7 @@ public abstract class MethodVisitor {
/**
* Visits a type instruction. A type instruction is an instruction that takes the internal name of
* a class as parameter.
* a class as parameter (see {@link Type#getInternalName()}).
*
* @param opcode the opcode of the type instruction to be visited. This opcode is either NEW,
* ANEWARRAY, CHECKCAST or INSTANCEOF.
@@ -552,12 +564,12 @@ public abstract class MethodVisitor {
/**
* Visits an IINC instruction.
*
* @param var index of the local variable to be incremented.
* @param varIndex index of the local variable to be incremented.
* @param increment amount to increment the local variable by.
*/
public void visitIincInsn(final int var, final int increment) {
public void visitIincInsn(final int varIndex, final int increment) {
if (mv != null) {
mv.visitIincInsn(var, increment);
mv.visitIincInsn(varIndex, increment);
}
}
@@ -643,8 +655,9 @@ public abstract class MethodVisitor {
* @param start the beginning of the exception handler's scope (inclusive).
* @param end the end of the exception handler's scope (exclusive).
* @param handler the beginning of the exception handler's code.
* @param type the internal name of the type of exceptions handled by the handler, or {@literal
* null} to catch any exceptions (for "finally" blocks).
* @param type the internal name of the type of exceptions handled by the handler (see {@link
* Type#getInternalName()}), or {@literal null} to catch any exceptions (for "finally"
* blocks).
* @throws IllegalArgumentException if one of the labels has already been visited by this visitor
* (by the {@link #visitLabel} method).
*/
@@ -284,6 +284,8 @@ public interface Opcodes {
int V17 = 0 << 16 | 61;
int V18 = 0 << 16 | 62;
int V19 = 0 << 16 | 63;
int V20 = 0 << 16 | 64;
int V21 = 0 << 16 | 65;
/**
* Version flag indicating that the class is using 'preview' features.
@@ -45,7 +45,7 @@ public abstract class RecordComponentVisitor {
/**
* The record visitor to which this visitor must delegate method calls. May be {@literal null}.
*/
/*package-private*/ RecordComponentVisitor delegate;
protected RecordComponentVisitor delegate;
/**
* Constructs a new {@link RecordComponentVisitor}.
@@ -83,7 +83,8 @@ public abstract class RecordComponentVisitor {
/**
* The record visitor to which this visitor must delegate method calls. May be {@literal null}.
*
* @return the record visitor to which this visitor must delegate method calls or {@literal null}.
* @return the record visitor to which this visitor must delegate method calls, or {@literal
* null}.
*/
public RecordComponentVisitor getDelegate() {
return delegate;
@@ -37,7 +37,7 @@ final class RecordComponentWriter extends RecordComponentVisitor {
/** The name_index field of the Record attribute. */
private final int nameIndex;
/** The descriptor_index field of the the Record attribute. */
/** The descriptor_index field of the Record attribute. */
private final int descriptorIndex;
/**
@@ -245,7 +245,7 @@ public final class Type {
/**
* Returns the {@link Type} corresponding to the given internal name.
*
* @param internalName an internal name.
* @param internalName an internal name (see {@link Type#getInternalName()}).
* @return the {@link Type} corresponding to the given internal name.
*/
public static Type getObjectType(final String internalName) {
@@ -708,8 +708,8 @@ public final class Type {
*
* @return the size of the arguments of the method (plus one for the implicit this argument),
* argumentsSize, and the size of its return value, returnSize, packed into a single int i =
* {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code
* i >> 2}, and returnSize to {@code i & 0x03}).
* {@code (argumentsSize &lt;&lt; 2) | returnSize} (argumentsSize is therefore equal to {@code
* i &gt;&gt; 2}, and returnSize to {@code i &amp; 0x03}).
*/
public int getArgumentsAndReturnSizes() {
return getArgumentsAndReturnSizes(getDescriptor());
@@ -721,8 +721,8 @@ public final class Type {
* @param methodDescriptor a method descriptor.
* @return the size of the arguments of the method (plus one for the implicit this argument),
* argumentsSize, and the size of its return value, returnSize, packed into a single int i =
* {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code
* i >> 2}, and returnSize to {@code i & 0x03}).
* {@code (argumentsSize &lt;&lt; 2) | returnSize} (argumentsSize is therefore equal to {@code
* i &gt;&gt; 2}, and returnSize to {@code i &amp; 0x03}).
*/
public static int getArgumentsAndReturnSizes(final String methodDescriptor) {
int argumentsSize = 1;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,9 +18,9 @@ package org.springframework.core;
/**
* Default implementation of the {@link ParameterNameDiscoverer} strategy interface,
* using the Java 8 standard reflection mechanism (if available), and falling back
* to the ASM-based {@link LocalVariableTableParameterNameDiscoverer} for checking
* debug information in the class file.
* using the Java 8 standard reflection mechanism, and falling back to the ASM-based
* {@link LocalVariableTableParameterNameDiscoverer} for checking debug information
* in the class file (e.g. for classes compiled with earlier Java versions).
*
* <p>If a Kotlin reflection implementation is present,
* {@link KotlinReflectionParameterNameDiscoverer} is added first in the list and
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2022 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.
@@ -31,11 +31,13 @@ import org.springframework.lang.Nullable;
* {@link ParameterNameDiscoverer} implementation which uses Kotlin's reflection facilities
* for introspecting parameter names.
*
* Compared to {@link StandardReflectionParameterNameDiscoverer}, it allows in addition to
* <p>Compared to {@link StandardReflectionParameterNameDiscoverer}, it allows in addition to
* determine interface parameter names without requiring Java 8 -parameters compiler flag.
*
* @author Sebastien Deleuze
* @since 5.0
* @see StandardReflectionParameterNameDiscoverer
* @see DefaultParameterNameDiscoverer
*/
public class KotlinReflectionParameterNameDiscoverer implements ParameterNameDiscoverer {
@@ -47,12 +47,18 @@ import org.springframework.util.ClassUtils;
* caches the ASM discovered information for each introspected Class, in a thread-safe
* manner. It is recommended to reuse ParameterNameDiscoverer instances as far as possible.
*
* <p>This discoverer variant is effectively superseded by the Java 8 based
* {@link StandardReflectionParameterNameDiscoverer} but included as a fallback still
* (for code not compiled with the standard "-parameters" compiler flag).
*
* @author Adrian Colyer
* @author Costin Leau
* @author Juergen Hoeller
* @author Chris Beams
* @author Sam Brannen
* @since 2.0
* @see StandardReflectionParameterNameDiscoverer
* @see DefaultParameterNameDiscoverer
*/
public class LocalVariableTableParameterNameDiscoverer implements ParameterNameDiscoverer {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2022 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,10 +26,15 @@ import org.springframework.lang.Nullable;
* {@link ParameterNameDiscoverer} implementation which uses JDK 8's reflection facilities
* for introspecting parameter names (based on the "-parameters" compiler flag).
*
* <p>This is a key element of {@link DefaultParameterNameDiscoverer} where it is being
* combined with {@link KotlinReflectionParameterNameDiscoverer} if Kotlin is present.
*
* @author Juergen Hoeller
* @since 4.0
* @see java.lang.reflect.Method#getParameters()
* @see java.lang.reflect.Parameter#getName()
* @see KotlinReflectionParameterNameDiscoverer
* @see DefaultParameterNameDiscoverer
*/
public class StandardReflectionParameterNameDiscoverer implements ParameterNameDiscoverer {
@@ -86,17 +86,6 @@ final class AttributeMethods {
}
/**
* Determine if this instance only contains a single attribute named
* {@code value}.
* @return {@code true} if there is only a value attribute
*/
boolean hasOnlyValueAttribute() {
return (this.attributeMethods.length == 1 &&
MergedAnnotation.VALUE.equals(this.attributeMethods[0].getName()));
}
/**
* Determine if values from the given annotation can be safely accessed without
* causing any {@link TypeNotPresentException TypeNotPresentExceptions}.
@@ -166,8 +166,8 @@ public abstract class RepeatableContainers {
private static Object computeRepeatedAnnotationsMethod(Class<? extends Annotation> annotationType) {
AttributeMethods methods = AttributeMethods.forAnnotationType(annotationType);
if (methods.hasOnlyValueAttribute()) {
Method method = methods.get(0);
Method method = methods.get(MergedAnnotation.VALUE);
if (method != null) {
Class<?> returnType = method.getReturnType();
if (returnType.isArray()) {
Class<?> componentType = returnType.getComponentType();
@@ -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.
@@ -22,12 +22,13 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* A simple log message type for use with Commons Logging, allowing
* for convenient lazy resolution of a given {@link Supplier} instance
* (typically bound to a Java 8 lambda expression) or a printf-style
* format string ({@link String#format}) in its {@link #toString()}.
* A simple log message type for use with Commons Logging, allowing for convenient
* lazy resolution of a given {@link Supplier} instance (typically bound to a lambda
* expression) or a printf-style format string ({@link String#format}) in its
* {@link #toString()}.
*
* @author Juergen Hoeller
* @author Sebastien Deleuze
* @since 5.2
* @see #of(Supplier)
* @see #format(String, Object)
@@ -77,7 +78,7 @@ public abstract class LogMessage implements CharSequence {
/**
* Build a lazily resolving message from the given supplier.
* @param supplier the supplier (typically bound to a Java 8 lambda expression)
* @param supplier the supplier (typically bound to a lambda expression)
* @see #toString()
*/
public static LogMessage of(Supplier<? extends CharSequence> supplier) {
@@ -87,63 +88,68 @@ public abstract class LogMessage implements CharSequence {
/**
* Build a lazily formatted message from the given format string and argument.
* @param format the format string (following {@link String#format} rules)
* @param arg1 the argument
* @param arg1 the argument (can be {@code null})
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, Object arg1) {
public static LogMessage format(String format, @Nullable Object arg1) {
return new FormatMessage1(format, arg1);
}
/**
* Build a lazily formatted message from the given format string and arguments.
* @param format the format string (following {@link String#format} rules)
* @param arg1 the first argument
* @param arg2 the second argument
* @param arg1 the first argument (can be {@code null})
* @param arg2 the second argument (can be {@code null})
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, Object arg1, Object arg2) {
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2) {
return new FormatMessage2(format, arg1, arg2);
}
/**
* Build a lazily formatted message from the given format string and arguments.
* @param format the format string (following {@link String#format} rules)
* @param arg1 the first argument
* @param arg2 the second argument
* @param arg3 the third argument
* @param arg1 the first argument (can be {@code null})
* @param arg2 the second argument (can be {@code null})
* @param arg3 the third argument (can be {@code null})
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, Object arg1, Object arg2, Object arg3) {
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3) {
return new FormatMessage3(format, arg1, arg2, arg3);
}
/**
* Build a lazily formatted message from the given format string and arguments.
* @param format the format string (following {@link String#format} rules)
* @param arg1 the first argument
* @param arg2 the second argument
* @param arg3 the third argument
* @param arg4 the fourth argument
* @param arg1 the first argument (can be {@code null})
* @param arg2 the second argument (can be {@code null})
* @param arg3 the third argument (can be {@code null})
* @param arg4 the fourth argument (can be {@code null})
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, Object arg1, Object arg2, Object arg3, Object arg4) {
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3,
@Nullable Object arg4) {
return new FormatMessage4(format, arg1, arg2, arg3, arg4);
}
/**
* Build a lazily formatted message from the given format string and varargs.
* <p>This varargs {@code format()} variant may be costly. You should therefore
* use the individual argument variants whenever possible:
* {@link #format(String, Object)}, {@link #format(String, Object, Object)}, etc.
* @param format the format string (following {@link String#format} rules)
* @param args the varargs array (costly, prefer individual arguments)
* @param args the varargs array (can be {@code null} and can contain {@code null}
* elements)
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, Object... args) {
public static LogMessage format(String format, @Nullable Object... args) {
return new FormatMessageX(format, args);
}
private static final class SupplierMessage extends LogMessage {
private Supplier<? extends CharSequence> supplier;
private final Supplier<? extends CharSequence> supplier;
SupplierMessage(Supplier<? extends CharSequence> supplier) {
Assert.notNull(supplier, "Supplier must not be null");
@@ -170,9 +176,10 @@ public abstract class LogMessage implements CharSequence {
private static final class FormatMessage1 extends FormatMessage {
@Nullable
private final Object arg1;
FormatMessage1(String format, Object arg1) {
FormatMessage1(String format, @Nullable Object arg1) {
super(format);
this.arg1 = arg1;
}
@@ -186,11 +193,13 @@ public abstract class LogMessage implements CharSequence {
private static final class FormatMessage2 extends FormatMessage {
@Nullable
private final Object arg1;
@Nullable
private final Object arg2;
FormatMessage2(String format, Object arg1, Object arg2) {
FormatMessage2(String format, @Nullable Object arg1, @Nullable Object arg2) {
super(format);
this.arg1 = arg1;
this.arg2 = arg2;
@@ -205,13 +214,16 @@ public abstract class LogMessage implements CharSequence {
private static final class FormatMessage3 extends FormatMessage {
@Nullable
private final Object arg1;
@Nullable
private final Object arg2;
@Nullable
private final Object arg3;
FormatMessage3(String format, Object arg1, Object arg2, Object arg3) {
FormatMessage3(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3) {
super(format);
this.arg1 = arg1;
this.arg2 = arg2;
@@ -227,15 +239,20 @@ public abstract class LogMessage implements CharSequence {
private static final class FormatMessage4 extends FormatMessage {
@Nullable
private final Object arg1;
@Nullable
private final Object arg2;
@Nullable
private final Object arg3;
@Nullable
private final Object arg4;
FormatMessage4(String format, Object arg1, Object arg2, Object arg3, Object arg4) {
FormatMessage4(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3,
@Nullable Object arg4) {
super(format);
this.arg1 = arg1;
this.arg2 = arg2;
@@ -252,9 +269,10 @@ public abstract class LogMessage implements CharSequence {
private static final class FormatMessageX extends FormatMessage {
@Nullable
private final Object[] args;
FormatMessageX(String format, Object... args) {
FormatMessageX(String format, @Nullable Object... args) {
super(format);
this.args = args;
}
@@ -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.
@@ -90,4 +90,9 @@ public class ExecutorServiceAdapter extends AbstractExecutorService {
return false;
}
// @Override on JDK 19
public void close() {
// no-op in order to avoid container-triggered shutdown call which would lead to exception logging
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -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.
@@ -64,7 +64,7 @@ public class InstanceFilter<T> {
/**
* Determine if the specified {code instance} matches this filter.
* Determine if the specified {@code instance} matches this filter.
*/
public boolean match(T instance) {
Assert.notNull(instance, "Instance to match must not be null");
@@ -0,0 +1,34 @@
/*
* Copyright 2002-2022 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;
import org.junit.platform.suite.api.IncludeClassNamePatterns;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
/**
* JUnit Platform based test suite for tests in the spring-core module.
*
* <p><strong>This suite is only intended to be used manually within an IDE.</strong>
*
* @author Sam Brannen
*/
@Suite
@SelectPackages({"org.springframework.core", "org.springframework.util"})
@IncludeClassNamePatterns(".*Tests?$")
class SpringCoreTestSuite {
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class LocalVariableTableParameterNameDiscovererTests {
private final LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
private final ParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
@Test
@@ -162,23 +162,23 @@ class LocalVariableTableParameterNameDiscovererTests {
Constructor<?> ctor = clazz.getDeclaredConstructor(Object.class);
String[] names = discoverer.getParameterNames(ctor);
assertThat(names.length).isEqualTo(1);
assertThat(names).hasSize(1);
assertThat(names[0]).isEqualTo("key");
ctor = clazz.getDeclaredConstructor(Object.class, Object.class);
names = discoverer.getParameterNames(ctor);
assertThat(names.length).isEqualTo(2);
assertThat(names).hasSize(2);
assertThat(names[0]).isEqualTo("key");
assertThat(names[1]).isEqualTo("value");
Method m = clazz.getMethod("generifiedStaticMethod", Object.class);
names = discoverer.getParameterNames(m);
assertThat(names.length).isEqualTo(1);
assertThat(names).hasSize(1);
assertThat(names[0]).isEqualTo("param");
m = clazz.getMethod("generifiedMethod", Object.class, long.class, Object.class, Object.class);
names = discoverer.getParameterNames(m);
assertThat(names.length).isEqualTo(4);
assertThat(names).hasSize(4);
assertThat(names[0]).isEqualTo("param");
assertThat(names[1]).isEqualTo("x");
assertThat(names[2]).isEqualTo("key");
@@ -186,21 +186,21 @@ class LocalVariableTableParameterNameDiscovererTests {
m = clazz.getMethod("voidStaticMethod", Object.class, long.class, int.class);
names = discoverer.getParameterNames(m);
assertThat(names.length).isEqualTo(3);
assertThat(names).hasSize(3);
assertThat(names[0]).isEqualTo("obj");
assertThat(names[1]).isEqualTo("x");
assertThat(names[2]).isEqualTo("i");
m = clazz.getMethod("nonVoidStaticMethod", Object.class, long.class, int.class);
names = discoverer.getParameterNames(m);
assertThat(names.length).isEqualTo(3);
assertThat(names).hasSize(3);
assertThat(names[0]).isEqualTo("obj");
assertThat(names[1]).isEqualTo("x");
assertThat(names[2]).isEqualTo("i");
m = clazz.getMethod("getDate");
names = discoverer.getParameterNames(m);
assertThat(names.length).isEqualTo(0);
assertThat(names).isEmpty();
}
@Disabled("Ignored because Ubuntu packages OpenJDK with debug symbols enabled. See SPR-8078.")
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -43,11 +43,10 @@ import static org.junit.jupiter.api.condition.JRE.JAVA_14;
@DisabledForJreRange(min = JAVA_14)
public class SpringCoreBlockHoundIntegrationTests {
@BeforeAll
static void setUp() {
static void setup() {
BlockHound.builder()
.with(new ReactorBlockHoundIntegration()) // Reactor non-blocking thread predicate
.with(new ReactorBlockHoundIntegration()) // Reactor non-blocking thread predicate
.with(new ReactiveAdapterRegistry.SpringCoreBlockHoundIntegration())
.install();
}
@@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -77,6 +78,7 @@ import static org.springframework.core.annotation.AnnotationUtilsTests.asArray;
* @see AnnotationUtilsTests
* @see MultipleComposedAnnotationsOnSingleAnnotatedElementTests
* @see ComposedRepeatableAnnotationsTests
* @see NestedRepeatableAnnotationsTests
*/
class AnnotatedElementUtilsTests {
@@ -908,6 +910,31 @@ class AnnotatedElementUtilsTests {
assertThat(annotation.value()).containsExactly("FromValueAttributeMeta");
}
/**
* @since 5.3.25
*/
@Test // gh-29685
void getMergedRepeatableAnnotationsWithContainerWithMultipleAttributes() {
Set<StandardRepeatableWithContainerWithMultipleAttributes> repeatableAnnotations =
AnnotatedElementUtils.getMergedRepeatableAnnotations(
StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class,
StandardRepeatableWithContainerWithMultipleAttributes.class);
assertThat(repeatableAnnotations).map(StandardRepeatableWithContainerWithMultipleAttributes::value)
.containsExactly("a", "b");
}
/**
* @since 5.3.25
*/
@Test // gh-29685
void findMergedRepeatableAnnotationsWithContainerWithMultipleAttributes() {
Set<StandardRepeatableWithContainerWithMultipleAttributes> repeatableAnnotations =
AnnotatedElementUtils.findMergedRepeatableAnnotations(
StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class,
StandardRepeatableWithContainerWithMultipleAttributes.class);
assertThat(repeatableAnnotations).map(StandardRepeatableWithContainerWithMultipleAttributes::value)
.containsExactly("a", "b");
}
// -------------------------------------------------------------------------
@@ -1557,4 +1584,24 @@ class AnnotatedElementUtilsTests {
static class ValueAttributeMetaMetaClass {
}
@Retention(RetentionPolicy.RUNTIME)
@interface StandardContainerWithMultipleAttributes {
StandardRepeatableWithContainerWithMultipleAttributes[] value();
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(StandardContainerWithMultipleAttributes.class)
@interface StandardRepeatableWithContainerWithMultipleAttributes {
String value() default "";
}
@StandardRepeatableWithContainerWithMultipleAttributes("a")
@StandardRepeatableWithContainerWithMultipleAttributes("b")
static class StandardRepeatablesWithContainerWithMultipleAttributesTestCase {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -58,24 +58,6 @@ class AttributeMethodsTests {
assertThat(getAll(methods)).flatExtracting(Method::getName).containsExactly("intValue", "value");
}
@Test
void hasOnlyValueAttributeWhenHasOnlyValueAttributeReturnsTrue() {
AttributeMethods methods = AttributeMethods.forAnnotationType(ValueOnly.class);
assertThat(methods.hasOnlyValueAttribute()).isTrue();
}
@Test
void hasOnlyValueAttributeWhenHasOnlySingleNonValueAttributeReturnsFalse() {
AttributeMethods methods = AttributeMethods.forAnnotationType(NonValueOnly.class);
assertThat(methods.hasOnlyValueAttribute()).isFalse();
}
@Test
void hasOnlyValueAttributeWhenHasOnlyMultipleAttributesIncludingValueReturnsFalse() {
AttributeMethods methods = AttributeMethods.forAnnotationType(MultipleAttributes.class);
assertThat(methods.hasOnlyValueAttribute()).isFalse();
}
@Test
void indexOfNameReturnsIndex() {
AttributeMethods methods = AttributeMethods.forAnnotationType(MultipleAttributes.class);
@@ -20,7 +20,9 @@ import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -31,193 +33,175 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* Tests for {@link RepeatableContainers}.
*
* @author Phillip Webb
* @author Sam Brannen
*/
class RepeatableContainersTests {
@Test
void standardRepeatablesWhenNonRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.standardRepeatables(), WithNonRepeatable.class,
NonRepeatable.class);
assertThat(values).isNull();
@Nested
class StandardRepeatableContainersTests {
@Test
void standardRepeatablesWhenNonRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(),
NonRepeatableTestCase.class, NonRepeatable.class);
assertThat(values).isNull();
}
@Test
void standardRepeatablesWhenSingleReturnsNull() {
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(),
SingleStandardRepeatableTestCase.class, StandardRepeatable.class);
assertThat(values).isNull();
}
@Test
void standardRepeatablesWhenContainerButNotRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(),
ExplicitRepeatablesTestCase.class, ExplicitContainer.class);
assertThat(values).isNull();
}
@Test
void standardRepeatablesWhenContainerReturnsRepeats() {
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(),
StandardRepeatablesTestCase.class, StandardContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
void standardRepeatablesWithContainerWithMultipleAttributes() {
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(),
StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class,
StandardContainerWithMultipleAttributes.class);
assertThat(values).containsExactly("a", "b");
}
}
@Test
void standardRepeatablesWhenSingleReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.standardRepeatables(),
WithSingleStandardRepeatable.class, StandardRepeatable.class);
assertThat(values).isNull();
}
@Nested
class ExplicitRepeatableContainerTests {
@Test
void standardRepeatablesWhenContainerReturnsRepeats() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.standardRepeatables(), WithStandardRepeatables.class,
StandardContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
void ofExplicitWhenNonRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class),
NonRepeatableTestCase.class, NonRepeatable.class);
assertThat(values).isNull();
}
@Test
void standardRepeatablesWhenContainerButNotRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.standardRepeatables(), WithExplicitRepeatables.class,
ExplicitContainer.class);
assertThat(values).isNull();
}
@Test
void ofExplicitWhenStandardRepeatableContainerReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class),
StandardRepeatablesTestCase.class, StandardContainer.class);
assertThat(values).isNull();
}
@Test
void ofExplicitWhenNonRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class),
WithNonRepeatable.class, NonRepeatable.class);
assertThat(values).isNull();
}
@Test
void ofExplicitWhenContainerReturnsRepeats() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class),
ExplicitRepeatablesTestCase.class, ExplicitContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
void ofExplicitWhenStandardRepeatableContainerReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class),
WithStandardRepeatables.class, StandardContainer.class);
assertThat(values).isNull();
}
@Test
void ofExplicitWhenContainerIsNullDeducesContainer() {
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.of(StandardRepeatable.class, null),
StandardRepeatablesTestCase.class, StandardContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
void ofExplicitWhenContainerReturnsRepeats() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class),
WithExplicitRepeatables.class, ExplicitContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
void ofExplicitWhenHasNoValueThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> RepeatableContainers.of(ExplicitRepeatable.class, InvalidNoValue.class))
.withMessageContaining("Invalid declaration of container type [%s] for repeatable annotation [%s]",
InvalidNoValue.class.getName(), ExplicitRepeatable.class.getName());
}
@Test
void ofExplicitWhenHasNoValueThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() ->
RepeatableContainers.of(ExplicitRepeatable.class, InvalidNoValue.class))
.withMessageContaining("Invalid declaration of container type ["
+ InvalidNoValue.class.getName()
+ "] for repeatable annotation ["
+ ExplicitRepeatable.class.getName() + "]");
}
@Test
void ofExplicitWhenValueIsNotArrayThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> RepeatableContainers.of(ExplicitRepeatable.class, InvalidNotArray.class))
.withMessage("Container type [%s] must declare a 'value' attribute for an array of type [%s]",
InvalidNotArray.class.getName(), ExplicitRepeatable.class.getName());
}
@Test
void ofExplicitWhenValueIsNotArrayThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() ->
RepeatableContainers.of(ExplicitRepeatable.class, InvalidNotArray.class))
.withMessage("Container type ["
+ InvalidNotArray.class.getName()
+ "] must declare a 'value' attribute for an array of type ["
+ ExplicitRepeatable.class.getName() + "]");
}
@Test
void ofExplicitWhenValueIsArrayOfWrongTypeThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> RepeatableContainers.of(ExplicitRepeatable.class, InvalidWrongArrayType.class))
.withMessage("Container type [%s] must declare a 'value' attribute for an array of type [%s]",
InvalidWrongArrayType.class.getName(), ExplicitRepeatable.class.getName());
}
@Test
void ofExplicitWhenValueIsArrayOfWrongTypeThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() ->
RepeatableContainers.of(ExplicitRepeatable.class, InvalidWrongArrayType.class))
.withMessage("Container type ["
+ InvalidWrongArrayType.class.getName()
+ "] must declare a 'value' attribute for an array of type ["
+ ExplicitRepeatable.class.getName() + "]");
}
@Test
void ofExplicitWhenAnnotationIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RepeatableContainers.of(null, null))
.withMessage("Repeatable must not be null");
}
@Test
void ofExplicitWhenAnnotationIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() ->
RepeatableContainers.of(null, null))
.withMessage("Repeatable must not be null");
}
@Test
void ofExplicitWhenContainerIsNullAndNotRepeatableThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RepeatableContainers.of(ExplicitRepeatable.class, null))
.withMessage("Annotation type must be a repeatable annotation: failed to resolve container type for %s",
ExplicitRepeatable.class.getName());
}
@Test
void ofExplicitWhenContainerIsNullDeducesContainer() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(StandardRepeatable.class, null),
WithStandardRepeatables.class, StandardContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
void ofExplicitWhenContainerIsNullAndNotRepeatableThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() ->
RepeatableContainers.of(ExplicitRepeatable.class, null))
.withMessage("Annotation type must be a repeatable annotation: " +
"failed to resolve container type for " +
ExplicitRepeatable.class.getName());
}
@Test
void standardAndExplicitReturnsRepeats() {
RepeatableContainers repeatableContainers = RepeatableContainers.standardRepeatables().and(
ExplicitContainer.class, ExplicitRepeatable.class);
assertThat(findRepeatedAnnotationValues(repeatableContainers,
WithStandardRepeatables.class, StandardContainer.class)).containsExactly(
"a", "b");
assertThat(findRepeatedAnnotationValues(repeatableContainers,
WithExplicitRepeatables.class, ExplicitContainer.class)).containsExactly(
"a", "b");
RepeatableContainers repeatableContainers = RepeatableContainers.standardRepeatables()
.and(ExplicitContainer.class, ExplicitRepeatable.class);
assertThat(findRepeatedAnnotationValues(repeatableContainers, StandardRepeatablesTestCase.class, StandardContainer.class))
.containsExactly("a", "b");
assertThat(findRepeatedAnnotationValues(repeatableContainers, ExplicitRepeatablesTestCase.class, ExplicitContainer.class))
.containsExactly("a", "b");
}
@Test
void noneAlwaysReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.none(), WithStandardRepeatables.class,
StandardContainer.class);
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.none(), StandardRepeatablesTestCase.class,
StandardContainer.class);
assertThat(values).isNull();
}
@Test
void equalsAndHashcode() {
RepeatableContainers c1 = RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class);
RepeatableContainers c2 = RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class);
RepeatableContainers c1 = RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class);
RepeatableContainers c2 = RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class);
RepeatableContainers c3 = RepeatableContainers.standardRepeatables();
RepeatableContainers c4 = RepeatableContainers.standardRepeatables().and(
ExplicitContainer.class, ExplicitRepeatable.class);
assertThat(c1.hashCode()).isEqualTo(c2.hashCode());
RepeatableContainers c4 = RepeatableContainers.standardRepeatables().and(ExplicitContainer.class, ExplicitRepeatable.class);
assertThat(c1).hasSameHashCodeAs(c2);
assertThat(c1).isEqualTo(c1).isEqualTo(c2);
assertThat(c1).isNotEqualTo(c3).isNotEqualTo(c4);
}
private Object[] findRepeatedAnnotationValues(RepeatableContainers containers,
private static Object[] findRepeatedAnnotationValues(RepeatableContainers containers,
Class<?> element, Class<? extends Annotation> annotationType) {
Annotation[] annotations = containers.findRepeatedAnnotations(
element.getAnnotation(annotationType));
Annotation[] annotations = containers.findRepeatedAnnotations(element.getAnnotation(annotationType));
return extractValues(annotations);
}
private Object[] extractValues(Annotation[] annotations) {
try {
if (annotations == null) {
return null;
}
Object[] result = new String[annotations.length];
for (int i = 0; i < annotations.length; i++) {
result[i] = annotations[i].annotationType().getMethod("value").invoke(
annotations[i]);
}
return result;
}
catch (Exception ex) {
throw new RuntimeException(ex);
private static Object[] extractValues(Annotation[] annotations) {
if (annotations == null) {
return null;
}
return Arrays.stream(annotations).map(AnnotationUtils::getValue).toArray(Object[]::new);
}
@Retention(RetentionPolicy.RUNTIME)
@interface NonRepeatable {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(StandardContainer.class)
@interface StandardRepeatable {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@interface StandardContainer {
@@ -225,7 +209,8 @@ class RepeatableContainersTests {
}
@Retention(RetentionPolicy.RUNTIME)
@interface ExplicitRepeatable {
@Repeatable(StandardContainer.class)
@interface StandardRepeatable {
String value() default "";
}
@@ -236,6 +221,12 @@ class RepeatableContainersTests {
ExplicitRepeatable[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@interface ExplicitRepeatable {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@interface InvalidNoValue {
}
@@ -253,20 +244,40 @@ class RepeatableContainersTests {
}
@NonRepeatable("a")
static class WithNonRepeatable {
static class NonRepeatableTestCase {
}
@StandardRepeatable("a")
static class WithSingleStandardRepeatable {
static class SingleStandardRepeatableTestCase {
}
@StandardRepeatable("a")
@StandardRepeatable("b")
static class WithStandardRepeatables {
static class StandardRepeatablesTestCase {
}
@Retention(RetentionPolicy.RUNTIME)
@interface StandardContainerWithMultipleAttributes {
StandardRepeatableWithContainerWithMultipleAttributes[] value();
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(StandardContainerWithMultipleAttributes.class)
@interface StandardRepeatableWithContainerWithMultipleAttributes {
String value() default "";
}
@StandardRepeatableWithContainerWithMultipleAttributes("a")
@StandardRepeatableWithContainerWithMultipleAttributes("b")
static class StandardRepeatablesWithContainerWithMultipleAttributesTestCase {
}
@ExplicitContainer({ @ExplicitRepeatable("a"), @ExplicitRepeatable("b") })
static class WithExplicitRepeatables {
static class ExplicitRepeatablesTestCase {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -251,32 +251,28 @@ class ObjectUtilsTests {
@Deprecated
void hashCodeWithDouble() {
double dbl = 9830.43;
int expected = (new Double(dbl)).hashCode();
assertThat(ObjectUtils.hashCode(dbl)).isEqualTo(expected);
assertThat(ObjectUtils.hashCode(dbl)).isEqualTo(Double.hashCode(dbl));
}
@Test
@Deprecated
void hashCodeWithFloat() {
float flt = 34.8f;
int expected = (Float.valueOf(flt)).hashCode();
assertThat(ObjectUtils.hashCode(flt)).isEqualTo(expected);
assertThat(ObjectUtils.hashCode(flt)).isEqualTo(Float.hashCode(flt));
}
@Test
@Deprecated
void hashCodeWithLong() {
long lng = 883L;
int expected = (Long.valueOf(lng)).hashCode();
assertThat(ObjectUtils.hashCode(lng)).isEqualTo(expected);
assertThat(ObjectUtils.hashCode(lng)).isEqualTo(Long.hashCode(lng));
}
@Test
void identityToString() {
Object obj = new Object();
String expected = obj.getClass().getName() + "@" + ObjectUtils.getIdentityHexString(obj);
String actual = ObjectUtils.identityToString(obj);
assertThat(actual).isEqualTo(expected);
assertThat(ObjectUtils.identityToString(obj)).isEqualTo(expected);
}
@Test
@@ -732,7 +728,7 @@ class ObjectUtilsTests {
@Test
void nullSafeToStringWithObjectArray() {
Object[] array = {"Han", Long.valueOf(43)};
Object[] array = {"Han", 43};
assertThat(ObjectUtils.nullSafeToString(array)).isEqualTo("{Han, 43}");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -33,8 +33,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link StreamUtils}.
@@ -57,53 +55,47 @@ class StreamUtilsTests {
@Test
void copyToByteArray() throws Exception {
InputStream inputStream = spy(new ByteArrayInputStream(bytes));
InputStream inputStream = new ByteArrayInputStream(bytes);
byte[] actual = StreamUtils.copyToByteArray(inputStream);
assertThat(actual).isEqualTo(bytes);
verify(inputStream, never()).close();
}
@Test
void copyToString() throws Exception {
Charset charset = Charset.defaultCharset();
InputStream inputStream = spy(new ByteArrayInputStream(string.getBytes(charset)));
InputStream inputStream = new ByteArrayInputStream(string.getBytes(charset));
String actual = StreamUtils.copyToString(inputStream, charset);
assertThat(actual).isEqualTo(string);
verify(inputStream, never()).close();
}
@Test
void copyBytes() throws Exception {
ByteArrayOutputStream out = spy(new ByteArrayOutputStream());
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamUtils.copy(bytes, out);
assertThat(out.toByteArray()).isEqualTo(bytes);
verify(out, never()).close();
}
@Test
void copyString() throws Exception {
Charset charset = Charset.defaultCharset();
ByteArrayOutputStream out = spy(new ByteArrayOutputStream());
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamUtils.copy(string, charset, out);
assertThat(out.toByteArray()).isEqualTo(string.getBytes(charset));
verify(out, never()).close();
}
@Test
void copyStream() throws Exception {
ByteArrayOutputStream out = spy(new ByteArrayOutputStream());
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamUtils.copy(new ByteArrayInputStream(bytes), out);
assertThat(out.toByteArray()).isEqualTo(bytes);
verify(out, never()).close();
}
@Test
void copyRange() throws Exception {
ByteArrayOutputStream out = spy(new ByteArrayOutputStream());
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamUtils.copyRange(new ByteArrayInputStream(bytes), out, 0, 100);
byte[] range = Arrays.copyOfRange(bytes, 0, 101);
assertThat(out.toByteArray()).isEqualTo(range);
verify(out, never()).close();
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -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.
@@ -264,7 +264,15 @@ public enum SpelMessage {
/** @since 5.3.17 */
MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED(Kind.ERROR, 1075,
"Array declares too many elements, exceeding the threshold of ''{0}''");
"Array declares too many elements, exceeding the threshold of ''{0}''"),
/** @since 5.3.26 */
MAX_REPEATED_TEXT_SIZE_EXCEEDED(Kind.ERROR, 1076,
"Repeated text results in too many characters, exceeding the threshold of ''{0}''"),
/** @since 5.3.26 */
MAX_REGEX_LENGTH_EXCEEDED(Kind.ERROR, 1077,
"Regular expression contains too many characters, exceeding the threshold of ''{0}''");
private final Kind kind;
@@ -22,6 +22,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
import org.springframework.asm.MethodVisitor;
import org.springframework.core.convert.TypeDescriptor;
@@ -46,10 +47,15 @@ import org.springframework.util.Assert;
* Represents the invocation of a constructor. Either a constructor on a regular type or
* construction of an array. When an array is constructed, an initializer can be specified.
*
* <p>Examples:<br>
* new String('hello world')<br>
* new int[]{1,2,3,4}<br>
* new int[3] new int[3]{1,2,3}
* <h4>Examples</h4>
* <ul>
* <li><code>new example.Foo()</code></li>
* <li><code>new String('hello world')</code></li>
* <li><code>new int[] {1,2,3,4}</code></li>
* <li><code>new String[] {'abc','xyz'}</code></li>
* <li><code>new int[5]</code></li>
* <li><code>new int[3][4]</code></li>
* </ul>
*
* @author Andy Clement
* @author Juergen Hoeller
@@ -68,7 +74,7 @@ public class ConstructorReference extends SpelNodeImpl {
private final boolean isArrayConstructor;
@Nullable
private SpelNodeImpl[] dimensions;
private final SpelNodeImpl[] dimensions;
// TODO is this caching safe - passing the expression around will mean this executor is also being passed around
/** The cached executor that may be reused on subsequent evaluations. */
@@ -83,6 +89,7 @@ public class ConstructorReference extends SpelNodeImpl {
public ConstructorReference(int startPos, int endPos, SpelNodeImpl... arguments) {
super(startPos, endPos, arguments);
this.isArrayConstructor = false;
this.dimensions = null;
}
/**
@@ -214,16 +221,33 @@ public class ConstructorReference extends SpelNodeImpl {
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder("new ");
int index = 0;
sb.append(getChild(index++).toStringAST());
sb.append('(');
for (int i = index; i < getChildCount(); i++) {
if (i > index) {
sb.append(',');
sb.append(getChild(0).toStringAST()); // constructor or array type
// Arrays
if (this.isArrayConstructor) {
if (hasInitializer()) {
// new int[] {1, 2, 3, 4, 5}, etc.
InlineList initializer = (InlineList) getChild(1);
sb.append("[] ").append(initializer.toStringAST());
}
else {
// new int[3], new java.lang.String[3][4], etc.
for (SpelNodeImpl dimension : this.dimensions) {
sb.append('[').append(dimension.toStringAST()).append(']');
}
}
sb.append(getChild(i).toStringAST());
}
sb.append(')');
// Constructors
else {
// new String('hello'), new org.example.Person('Jane', 32), etc.
StringJoiner sj = new StringJoiner(",", "(", ")");
int count = getChildCount();
for (int i = 1; i < count; i++) {
sj.add(getChild(i).toStringAST());
}
sb.append(sj.toString());
}
return sb.toString();
}
@@ -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.
@@ -144,7 +144,7 @@ public class FunctionReference extends SpelNodeImpl {
for (int i = 0; i < getChildCount(); i++) {
sj.add(getChild(i).toStringAST());
}
return '#' + this.name + sj.toString();
return '#' + this.name + sj;
}
/**
@@ -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.
@@ -263,7 +263,7 @@ public class MethodReference extends SpelNodeImpl {
for (int i = 0; i < getChildCount(); i++) {
sj.add(getChild(i).toStringAST());
}
return this.name + sj.toString();
return this.name + sj;
}
/**
@@ -288,12 +288,9 @@ public class MethodReference extends SpelNodeImpl {
if (executor.didArgumentConversionOccur()) {
return false;
}
Class<?> clazz = executor.getMethod().getDeclaringClass();
if (!Modifier.isPublic(clazz.getModifiers()) && executor.getPublicDeclaringClass() == null) {
return false;
}
return true;
Class<?> clazz = executor.getMethod().getDeclaringClass();
return (Modifier.isPublic(clazz.getModifiers()) || executor.getPublicDeclaringClass() != null);
}
@Override
@@ -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.
@@ -25,6 +25,8 @@ import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
@@ -52,6 +54,13 @@ import org.springframework.util.NumberUtils;
*/
public class OpMultiply extends Operator {
/**
* Maximum number of characters permitted in repeated text.
* @since 5.3.26
*/
private static final int MAX_REPEATED_TEXT_SIZE = 256;
public OpMultiply(int startPos, int endPos, SpelNodeImpl... operands) {
super("*", startPos, endPos, operands);
}
@@ -109,10 +118,13 @@ public class OpMultiply extends Operator {
}
if (leftOperand instanceof String && rightOperand instanceof Integer) {
int repeats = (Integer) rightOperand;
StringBuilder result = new StringBuilder();
for (int i = 0; i < repeats; i++) {
result.append(leftOperand);
String text = (String) leftOperand;
int count = (Integer) rightOperand;
int requestedSize = text.length() * count;
checkRepeatedTextSize(requestedSize);
StringBuilder result = new StringBuilder(requestedSize);
for (int i = 0; i < count; i++) {
result.append(text);
}
return new TypedValue(result.toString());
}
@@ -120,6 +132,13 @@ public class OpMultiply extends Operator {
return state.operate(Operation.MULTIPLY, leftOperand, rightOperand);
}
private void checkRepeatedTextSize(int requestedSize) {
if (requestedSize > MAX_REPEATED_TEXT_SIZE) {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED, MAX_REPEATED_TEXT_SIZE);
}
}
@Override
public boolean isCompilable() {
if (!getLeftOperand().isCompilable()) {
@@ -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.
@@ -36,19 +36,41 @@ import org.springframework.expression.spel.support.BooleanTypedValue;
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
*/
public class OperatorMatches extends Operator {
private static final int PATTERN_ACCESS_THRESHOLD = 1000000;
private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>();
/**
* Maximum number of characters permitted in a regular expression.
* @since 5.3.26
*/
private static final int MAX_REGEX_LENGTH = 256;
private final ConcurrentMap<String, Pattern> patternCache;
/**
* Create a new {@link OperatorMatches} instance.
* @deprecated as of Spring Framework 5.3.26 in favor of invoking
* {@link #OperatorMatches(ConcurrentMap, int, int, SpelNodeImpl...)}
* with a shared pattern cache instead
*/
@Deprecated
public OperatorMatches(int startPos, int endPos, SpelNodeImpl... operands) {
super("matches", startPos, endPos, operands);
this(new ConcurrentHashMap<>(), startPos, endPos, operands);
}
/**
* Create a new {@link OperatorMatches} instance with a shared pattern cache.
* @since 5.3.26
*/
public OperatorMatches(ConcurrentMap<String, Pattern> patternCache, int startPos, int endPos, SpelNodeImpl... operands) {
super("matches", startPos, endPos, operands);
this.patternCache = patternCache;
}
/**
* Check the first operand matches the regex specified as the second operand.
@@ -62,26 +84,28 @@ public class OperatorMatches extends Operator {
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
SpelNodeImpl leftOp = getLeftOperand();
SpelNodeImpl rightOp = getRightOperand();
String left = leftOp.getValue(state, String.class);
Object right = getRightOperand().getValue(state);
if (left == null) {
String input = leftOp.getValue(state, String.class);
if (input == null) {
throw new SpelEvaluationException(leftOp.getStartPosition(),
SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, (Object) null);
}
Object right = rightOp.getValue(state);
if (!(right instanceof String)) {
throw new SpelEvaluationException(rightOp.getStartPosition(),
SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, right);
}
String regex = (String) right;
try {
String rightString = (String) right;
Pattern pattern = this.patternCache.get(rightString);
Pattern pattern = this.patternCache.get(regex);
if (pattern == null) {
pattern = Pattern.compile(rightString);
this.patternCache.putIfAbsent(rightString, pattern);
checkRegexLength(regex);
pattern = Pattern.compile(regex);
this.patternCache.putIfAbsent(regex, pattern);
}
Matcher matcher = pattern.matcher(new MatcherInput(left, new AccessCount()));
Matcher matcher = pattern.matcher(new MatcherInput(input, new AccessCount()));
return BooleanTypedValue.forValue(matcher.matches());
}
catch (PatternSyntaxException ex) {
@@ -94,6 +118,13 @@ public class OperatorMatches extends Operator {
}
}
private void checkRegexLength(String regex) {
if (regex.length() > MAX_REGEX_LENGTH) {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.MAX_REGEX_LENGTH_EXCEEDED, MAX_REGEX_LENGTH);
}
}
private static class AccessCount {
@@ -111,7 +142,7 @@ public class OperatorMatches extends Operator {
private final CharSequence value;
private AccessCount access;
private final AccessCount access;
public MatcherInput(CharSequence value, AccessCount access) {
this.value = value;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.springframework.util.StringUtils;
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
*/
public class StringLiteral extends Literal {
@@ -36,9 +37,19 @@ public class StringLiteral extends Literal {
public StringLiteral(String payload, int startPos, int endPos, String value) {
super(payload, startPos, endPos);
// The original enclosing quote character for the string literal: ' or ".
char quoteCharacter = value.charAt(0);
// Remove enclosing quotes
String valueWithinQuotes = value.substring(1, value.length() - 1);
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "''", "'");
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "\"\"", "\"");
// Replace escaped internal quote characters
if (quoteCharacter == '\'') {
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "''", "'");
}
else {
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "\"\"", "\"");
}
this.value = new TypedValue(valueWithinQuotes);
this.exitTypeDescriptor = "Ljava/lang/String";
@@ -52,7 +63,9 @@ public class StringLiteral extends Literal {
@Override
public String toString() {
return "'" + getLiteralValue().getValue() + "'";
String ast = String.valueOf(getLiteralValue().getValue());
ast = StringUtils.replace(ast, "'", "''");
return "'" + ast + "'";
}
@Override
@@ -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.
@@ -21,6 +21,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;
import org.springframework.expression.ParseException;
@@ -83,6 +85,7 @@ import org.springframework.util.StringUtils;
* @author Andy Clement
* @author Juergen Hoeller
* @author Phillip Webb
* @author Sam Brannen
* @since 3.0
*/
class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
@@ -95,6 +98,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
// For rules that build nodes, they are stacked here for return
private final Deque<SpelNodeImpl> constructedNodes = new ArrayDeque<>();
// Shared cache for compiled regex patterns
private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>();
// The expression being parsed
private String expressionString = "";
@@ -248,7 +254,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
if (tk == TokenKind.MATCHES) {
return new OperatorMatches(t.startPos, t.endPos, expr, rhExpr);
return new OperatorMatches(this.patternCache, t.startPos, t.endPos, expr, rhExpr);
}
Assert.isTrue(tk == TokenKind.BETWEEN, "Between token expected");
@@ -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.
@@ -48,6 +48,7 @@ import org.springframework.lang.Nullable;
* @author Andy Clement
* @author Juergen Hoeller
* @author Chris Beams
* @author Sam Brannen
* @since 3.0
* @see StandardEvaluationContext#addMethodResolver(MethodResolver)
*/
@@ -225,8 +226,7 @@ public class ReflectiveMethodResolver implements MethodResolver {
if (targetObject instanceof Class) {
Set<Method> result = new LinkedHashSet<>();
// Add these so that static methods are invocable on the type: e.g. Float.valueOf(..)
Method[] methods = getMethods(type);
for (Method method : methods) {
for (Method method : getMethods(type)) {
if (Modifier.isStatic(method.getModifiers())) {
result.add(method);
}
@@ -239,19 +239,23 @@ public class ReflectiveMethodResolver implements MethodResolver {
Set<Method> result = new LinkedHashSet<>();
// Expose interface methods (not proxy-declared overrides) for proper vararg introspection
for (Class<?> ifc : type.getInterfaces()) {
Method[] methods = getMethods(ifc);
for (Method method : methods) {
for (Method method : getMethods(ifc)) {
if (isCandidateForInvocation(method, type)) {
result.add(method);
}
}
}
// Ensure methods defined in java.lang.Object are exposed for JDK proxies.
for (Method method : getMethods(Object.class)) {
if (isCandidateForInvocation(method, type)) {
result.add(method);
}
}
return result;
}
else {
Set<Method> result = new LinkedHashSet<>();
Method[] methods = getMethods(type);
for (Method method : methods) {
for (Method method : getMethods(type)) {
if (isCandidateForInvocation(method, type)) {
result.add(method);
}
@@ -276,7 +280,7 @@ public class ReflectiveMethodResolver implements MethodResolver {
* Determine whether the given {@code Method} is a candidate for method resolution
* on an instance of the given target class.
* <p>The default implementation considers any method as a candidate, even for
* static methods sand non-user-declared methods on the {@link Object} base class.
* static methods and non-user-declared methods on the {@link Object} base class.
* @param method the Method to evaluate
* @param targetClass the concrete target class that is being introspected
* @since 4.3.15
@@ -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.
@@ -35,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*/
public abstract class AbstractExpressionTests {
private static final boolean DEBUG = false;
protected static final boolean DEBUG = false;
protected static final boolean SHOULD_BE_WRITABLE = true;
@@ -202,7 +202,9 @@ public abstract class AbstractExpressionTests {
protected void parseAndCheckError(String expression, SpelMessage expectedMessage, Object... otherProperties) {
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
Expression expr = parser.parseExpression(expression);
SpelUtilities.printAbstractSyntaxTree(System.out, expr);
if (DEBUG) {
SpelUtilities.printAbstractSyntaxTree(System.out, expr);
}
}).satisfies(ex -> {
assertThat(ex.getMessageCode()).isEqualTo(expectedMessage);
if (otherProperties != null && otherProperties.length != 0) {
@@ -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.
@@ -127,17 +127,6 @@ class EvaluationTests extends AbstractExpressionTests {
evaluate("null?.null?.null", null, null);
}
@Test // SPR-16731
void matchesWithPatternAccessThreshold() {
String pattern = "^(?=[a-z0-9-]{1,47})([a-z0-9]+[-]{0,1}){1,47}[a-z0-9]{1}$";
String expression = "'abcde-fghijklmn-o42pasdfasdfasdf.qrstuvwxyz10x.xx.yyy.zasdfasfd' matches \'" + pattern + "\'";
Expression expr = parser.parseExpression(expression);
assertThatExceptionOfType(SpelEvaluationException.class)
.isThrownBy(expr::getValue)
.withCauseInstanceOf(IllegalStateException.class)
.satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.FLAWED_PATTERN));
}
// mixing operators
@Test
void mixingOperators() {
@@ -364,6 +353,53 @@ class EvaluationTests extends AbstractExpressionTests {
}
@Nested
class StringLiterals {
@Test
void insideSingleQuotes() {
evaluate("'hello'", "hello", String.class);
evaluate("'hello world'", "hello world", String.class);
}
@Test
void insideDoubleQuotes() {
evaluate("\"hello\"", "hello", String.class);
evaluate("\"hello world\"", "hello world", String.class);
}
@Test
void singleQuotesInsideSingleQuotes() {
evaluate("'Tony''s Pizza'", "Tony's Pizza", String.class);
evaluate("'big ''''pizza'''' parlor'", "big ''pizza'' parlor", String.class);
}
@Test
void doubleQuotesInsideDoubleQuotes() {
evaluate("\"big \"\"pizza\"\" parlor\"", "big \"pizza\" parlor", String.class);
evaluate("\"big \"\"\"\"pizza\"\"\"\" parlor\"", "big \"\"pizza\"\" parlor", String.class);
}
@Test
void singleQuotesInsideDoubleQuotes() {
evaluate("\"Tony's Pizza\"", "Tony's Pizza", String.class);
evaluate("\"big ''pizza'' parlor\"", "big ''pizza'' parlor", String.class);
}
@Test
void doubleQuotesInsideSingleQuotes() {
evaluate("'big \"pizza\" parlor'", "big \"pizza\" parlor", String.class);
evaluate("'two double \"\" quotes'", "two double \"\" quotes", String.class);
}
@Test
void inCompoundExpressions() {
evaluate("'123''4' == '123''4'", true, Boolean.class);
evaluate("\"123\"\"4\" == \"123\"\"4\"", true, Boolean.class);
}
}
@Nested
class RelationalOperatorTests {
@@ -413,28 +449,49 @@ class EvaluationTests extends AbstractExpressionTests {
}
@Test
void relOperatorsMatches01() {
evaluate("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "false", Boolean.class);
}
@Test
void relOperatorsMatches02() {
void matchesTrue() {
evaluate("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "true", Boolean.class);
}
@Test
void relOperatorsMatches03() {
void matchesFalse() {
evaluate("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "false", Boolean.class);
}
@Test
void matchesWithInputConversion() {
evaluate("27 matches '^.*2.*$'", true, Boolean.class); // conversion int --> string
}
@Test
void matchesWithNullInput() {
evaluateAndCheckError("null matches '^.*$'", SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, 0, null);
}
@Test
void relOperatorsMatches04() {
void matchesWithNullPattern() {
evaluateAndCheckError("'abc' matches null", SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, 14, null);
}
@Test // SPR-16731
void matchesWithPatternAccessThreshold() {
String pattern = "^(?=[a-z0-9-]{1,47})([a-z0-9]+[-]{0,1}){1,47}[a-z0-9]{1}$";
String expression = "'abcde-fghijklmn-o42pasdfasdfasdf.qrstuvwxyz10x.xx.yyy.zasdfasfd' matches '" + pattern + "'";
evaluateAndCheckError(expression, SpelMessage.FLAWED_PATTERN);
}
@Test
void relOperatorsMatches05() {
evaluate("27 matches '^.*2.*$'", true, Boolean.class); // conversion int>string
void matchesWithPatternLengthThreshold() {
String pattern = "(0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
"01234567890123456789012345678901234567890123456789|abc)";
assertThat(pattern).hasSize(256);
Expression expr = parser.parseExpression("'abc' matches '" + pattern + "'");
assertThat(expr.getValue(context, Boolean.class)).isTrue();
pattern += "?";
assertThat(pattern).hasSize(257);
evaluateAndCheckError("'abc' matches '" + pattern + "'", Boolean.class, SpelMessage.MAX_REGEX_LENGTH_EXCEEDED);
}
}
@@ -1401,7 +1458,9 @@ class EvaluationTests extends AbstractExpressionTests {
private void expectFail(ExpressionParser parser, EvaluationContext eContext, String expressionString, SpelMessage messageCode) {
assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> {
Expression e = parser.parseExpression(expressionString);
SpelUtilities.printAbstractSyntaxTree(System.out, e);
if (DEBUG) {
SpelUtilities.printAbstractSyntaxTree(System.out, e);
}
e.getValue(eContext);
}).satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(messageCode));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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,52 +29,70 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Andy Clement
*/
public class LiteralExpressionTests {
class LiteralExpressionTests {
private final LiteralExpression lEx = new LiteralExpression("somevalue");
@Test
public void testGetValue() throws Exception {
LiteralExpression lEx = new LiteralExpression("somevalue");
assertThat(lEx.getValue()).isInstanceOf(String.class).isEqualTo("somevalue");
assertThat(lEx.getValue(String.class)).isInstanceOf(String.class).isEqualTo("somevalue");
void getValue() throws Exception {
assertThat(lEx.getValue()).isEqualTo("somevalue");
assertThat(lEx.getValue(String.class)).isEqualTo("somevalue");
assertThat(lEx.getValue(new Rooty())).isEqualTo("somevalue");
assertThat(lEx.getValue(new Rooty(), String.class)).isEqualTo("somevalue");
}
@Test
void getValueWithSuppliedEvaluationContext() throws Exception {
EvaluationContext ctx = new StandardEvaluationContext();
assertThat(lEx.getValue(ctx)).isInstanceOf(String.class).isEqualTo("somevalue");
assertThat(lEx.getValue(ctx, String.class)).isInstanceOf(String.class).isEqualTo("somevalue");
assertThat(lEx.getValue(new Rooty())).isInstanceOf(String.class).isEqualTo("somevalue");
assertThat(lEx.getValue(new Rooty(), String.class)).isInstanceOf(String.class).isEqualTo("somevalue");
assertThat(lEx.getValue(ctx, new Rooty())).isInstanceOf(String.class).isEqualTo("somevalue");
assertThat(lEx.getValue(ctx, new Rooty(),String.class)).isInstanceOf(String.class).isEqualTo("somevalue");
assertThat(lEx.getValue(ctx)).isEqualTo("somevalue");
assertThat(lEx.getValue(ctx, String.class)).isEqualTo("somevalue");
assertThat(lEx.getValue(ctx, new Rooty())).isEqualTo("somevalue");
assertThat(lEx.getValue(ctx, new Rooty(), String.class)).isEqualTo("somevalue");
}
@Test
void getExpressionString() {
assertThat(lEx.getExpressionString()).isEqualTo("somevalue");
}
@Test
void isWritable() throws Exception {
assertThat(lEx.isWritable(new StandardEvaluationContext())).isFalse();
assertThat(lEx.isWritable(new Rooty())).isFalse();
assertThat(lEx.isWritable(new StandardEvaluationContext(), new Rooty())).isFalse();
}
static class Rooty {}
@Test
public void testSetValue() {
assertThatExceptionOfType(EvaluationException.class).isThrownBy(() ->
new LiteralExpression("somevalue").setValue(new StandardEvaluationContext(), "flibble"))
void setValue() {
assertThatExceptionOfType(EvaluationException.class)
.isThrownBy(() -> lEx.setValue(new StandardEvaluationContext(), "flibble"))
.satisfies(ex -> assertThat(ex.getExpressionString()).isEqualTo("somevalue"));
assertThatExceptionOfType(EvaluationException.class).isThrownBy(() ->
new LiteralExpression("somevalue").setValue(new Rooty(), "flibble"))
assertThatExceptionOfType(EvaluationException.class)
.isThrownBy(() -> lEx.setValue(new Rooty(), "flibble"))
.satisfies(ex -> assertThat(ex.getExpressionString()).isEqualTo("somevalue"));
assertThatExceptionOfType(EvaluationException.class).isThrownBy(() ->
new LiteralExpression("somevalue").setValue(new StandardEvaluationContext(), new Rooty(), "flibble"))
assertThatExceptionOfType(EvaluationException.class)
.isThrownBy(() -> lEx.setValue(new StandardEvaluationContext(), new Rooty(), "flibble"))
.satisfies(ex -> assertThat(ex.getExpressionString()).isEqualTo("somevalue"));
}
@Test
public void testGetValueType() throws Exception {
LiteralExpression lEx = new LiteralExpression("somevalue");
void getValueType() throws Exception {
assertThat(lEx.getValueType()).isEqualTo(String.class);
assertThat(lEx.getValueType(new StandardEvaluationContext())).isEqualTo(String.class);
assertThat(lEx.getValueType(new Rooty())).isEqualTo(String.class);
assertThat(lEx.getValueType(new StandardEvaluationContext(), new Rooty())).isEqualTo(String.class);
}
@Test
void getValueTypeDescriptor() throws Exception {
assertThat(lEx.getValueTypeDescriptor().getType()).isEqualTo(String.class);
assertThat(lEx.getValueTypeDescriptor(new StandardEvaluationContext()).getType()).isEqualTo(String.class);
assertThat(lEx.getValueTypeDescriptor(new Rooty()).getType()).isEqualTo(String.class);
assertThat(lEx.getValueTypeDescriptor(new StandardEvaluationContext(), new Rooty()).getType()).isEqualTo(String.class);
}
static class Rooty {}
}
@@ -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.
@@ -21,22 +21,25 @@ import java.math.BigInteger;
import org.junit.jupiter.api.Test;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.ast.Operator;
import org.springframework.expression.spel.standard.SpelExpression;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.expression.spel.SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED;
/**
* Tests the evaluation of expressions using relational operators.
* Tests the evaluation of expressions using various operators.
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Giovanni Dall'Oglio Risso
* @author Sam Brannen
*/
class OperatorTests extends AbstractExpressionTests {
@Test
void testEqual() {
void equal() {
evaluate("3 == 5", false, Boolean.class);
evaluate("5 == 3", false, Boolean.class);
evaluate("6 == 6", true, Boolean.class);
@@ -85,7 +88,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testNotEqual() {
void notEqual() {
evaluate("3 != 5", true, Boolean.class);
evaluate("5 != 3", true, Boolean.class);
evaluate("6 != 6", false, Boolean.class);
@@ -134,7 +137,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testLessThan() {
void lessThan() {
evaluate("5 < 5", false, Boolean.class);
evaluate("3 < 5", true, Boolean.class);
evaluate("5 < 3", false, Boolean.class);
@@ -176,7 +179,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testLessThanOrEqual() {
void lessThanOrEqual() {
evaluate("3 <= 5", true, Boolean.class);
evaluate("5 <= 3", false, Boolean.class);
evaluate("6 <= 6", true, Boolean.class);
@@ -225,7 +228,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testGreaterThan() {
void greaterThan() {
evaluate("3 > 5", false, Boolean.class);
evaluate("5 > 3", true, Boolean.class);
evaluate("3L > 5L", false, Boolean.class);
@@ -266,7 +269,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testGreaterThanOrEqual() {
void greaterThanOrEqual() {
evaluate("3 >= 5", false, Boolean.class);
evaluate("5 >= 3", true, Boolean.class);
evaluate("6 >= 6", true, Boolean.class);
@@ -315,27 +318,22 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testIntegerLiteral() {
void integerLiteral() {
evaluate("3", 3, Integer.class);
}
@Test
void testRealLiteral() {
void realLiteral() {
evaluate("3.5", 3.5d, Double.class);
}
@Test
void testMultiplyStringInt() {
evaluate("'a' * 5", "aaaaa", String.class);
}
@Test
void testMultiplyDoubleDoubleGivesDouble() {
void multiplyDoubleDoubleGivesDouble() {
evaluate("3.0d * 5.0d", 15.0d, Double.class);
}
@Test
void testMixedOperandsBigDecimal() {
void mixedOperandsBigDecimal() {
evaluate("3 * new java.math.BigDecimal('5')", new BigDecimal("15"), BigDecimal.class);
evaluate("3L * new java.math.BigDecimal('5')", new BigDecimal("15"), BigDecimal.class);
evaluate("3.0d * new java.math.BigDecimal('5')", new BigDecimal("15.0"), BigDecimal.class);
@@ -361,19 +359,19 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testMathOperatorAdd02() {
void mathOperatorAdd02() {
evaluate("'hello' + ' ' + 'world'", "hello world", String.class);
}
@Test
void testMathOperatorsInChains() {
void mathOperatorsInChains() {
evaluate("1+2+3",6,Integer.class);
evaluate("2*3*4",24,Integer.class);
evaluate("12-1-2",9,Integer.class);
}
@Test
void testIntegerArithmetic() {
void integerArithmetic() {
evaluate("2 + 4", "6", Integer.class);
evaluate("5 - 4", "1", Integer.class);
evaluate("3 * 5", 15, Integer.class);
@@ -388,7 +386,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testPlus() {
void plus() {
evaluate("7 + 2", "9", Integer.class);
evaluate("3.0f + 5.0f", 8.0f, Float.class);
evaluate("3.0d + 5.0d", 8.0d, Double.class);
@@ -400,44 +398,44 @@ class OperatorTests extends AbstractExpressionTests {
evaluate("null + 'ab'", "nullab", String.class);
// AST:
SpelExpression expr = (SpelExpression)parser.parseExpression("+3");
SpelExpression expr = (SpelExpression) parser.parseExpression("+3");
assertThat(expr.toStringAST()).isEqualTo("+3");
expr = (SpelExpression)parser.parseExpression("2+3");
expr = (SpelExpression) parser.parseExpression("2+3");
assertThat(expr.toStringAST()).isEqualTo("(2 + 3)");
// use as a unary operator
evaluate("+5d",5d,Double.class);
evaluate("+5L",5L,Long.class);
evaluate("+5",5,Integer.class);
evaluate("+new java.math.BigDecimal('5')", new BigDecimal("5"),BigDecimal.class);
evaluateAndCheckError("+'abc'",SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
evaluate("+5d", 5d, Double.class);
evaluate("+5L", 5L, Long.class);
evaluate("+5", 5, Integer.class);
evaluate("+new java.math.BigDecimal('5')", new BigDecimal("5"), BigDecimal.class);
evaluateAndCheckError("+'abc'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
// string concatenation
evaluate("'abc'+'def'","abcdef",String.class);
evaluate("'abc'+'def'", "abcdef", String.class);
evaluate("5 + new Integer('37')",42,Integer.class);
evaluate("5 + new Integer('37')", 42, Integer.class);
}
@Test
void testMinus() {
void minus() {
evaluate("'c' - 2", "a", String.class);
evaluate("3.0f - 5.0f", -2.0f, Float.class);
evaluateAndCheckError("'ab' - 2", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
evaluateAndCheckError("2-'ab'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
SpelExpression expr = (SpelExpression)parser.parseExpression("-3");
SpelExpression expr = (SpelExpression) parser.parseExpression("-3");
assertThat(expr.toStringAST()).isEqualTo("-3");
expr = (SpelExpression)parser.parseExpression("2-3");
expr = (SpelExpression) parser.parseExpression("2-3");
assertThat(expr.toStringAST()).isEqualTo("(2 - 3)");
evaluate("-5d",-5d,Double.class);
evaluate("-5L",-5L,Long.class);
evaluate("-5d", -5d, Double.class);
evaluate("-5L", -5L, Long.class);
evaluate("-5", -5, Integer.class);
evaluate("-new java.math.BigDecimal('5')", new BigDecimal("-5"),BigDecimal.class);
evaluate("-new java.math.BigDecimal('5')", new BigDecimal("-5"), BigDecimal.class);
evaluateAndCheckError("-'abc'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
}
@Test
void testModulus() {
void modulus() {
evaluate("3%2",1,Integer.class);
evaluate("3L%2L",1L,Long.class);
evaluate("3.0f%2.0f",1f,Float.class);
@@ -448,7 +446,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testDivide() {
void divide() {
evaluate("3.0f / 5.0f", 0.6f, Float.class);
evaluate("4L/2L",2L,Long.class);
evaluate("3.0f div 5.0f", 0.6f, Float.class);
@@ -461,17 +459,17 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testMathOperatorDivide_ConvertToDouble() {
void mathOperatorDivide_ConvertToDouble() {
evaluateAndAskForReturnType("8/4", 2.0, Double.class);
}
@Test
void testMathOperatorDivide04_ConvertToFloat() {
void mathOperatorDivide04_ConvertToFloat() {
evaluateAndAskForReturnType("8/4", 2.0F, Float.class);
}
@Test
void testDoubles() {
void doubles() {
evaluate("3.0d == 5.0d", false, Boolean.class);
evaluate("3.0d == 3.0d", true, Boolean.class);
evaluate("3.0d != 5.0d", true, Boolean.class);
@@ -484,7 +482,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testBigDecimals() {
void bigDecimals() {
evaluate("3 + new java.math.BigDecimal('5')", new BigDecimal("8"), BigDecimal.class);
evaluate("3 - new java.math.BigDecimal('5')", new BigDecimal("-2"), BigDecimal.class);
evaluate("3 * new java.math.BigDecimal('5')", new BigDecimal("15"), BigDecimal.class);
@@ -495,7 +493,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testOperatorNames() {
void operatorNames() {
Operator node = getOperatorNode((SpelExpression)parser.parseExpression("1==3"));
assertThat(node.getOperatorName()).isEqualTo("==");
@@ -534,22 +532,22 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testOperatorOverloading() {
void operatorOverloading() {
evaluateAndCheckError("'a' * '2'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
evaluateAndCheckError("'a' ^ '2'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
}
@Test
void testPower() {
evaluate("3^2",9,Integer.class);
evaluate("3.0d^2.0d",9.0d,Double.class);
evaluate("3L^2L",9L,Long.class);
void power() {
evaluate("3^2", 9, Integer.class);
evaluate("3.0d^2.0d", 9.0d, Double.class);
evaluate("3L^2L", 9L, Long.class);
evaluate("(2^32)^2", 9223372036854775807L, Long.class);
evaluate("new java.math.BigDecimal('5') ^ 3", new BigDecimal("125"), BigDecimal.class);
}
@Test
void testMixedOperands_FloatsAndDoubles() {
void mixedOperands_FloatsAndDoubles() {
evaluate("3.0d + 5.0f", 8.0d, Double.class);
evaluate("3.0D - 5.0f", -2.0d, Double.class);
evaluate("3.0f * 5.0d", 15.0d, Double.class);
@@ -558,7 +556,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testMixedOperands_DoublesAndInts() {
void mixedOperands_DoublesAndInts() {
evaluate("3.0d + 5", 8.0d, Double.class);
evaluate("3.0D - 5", -2.0d, Double.class);
evaluate("3.0f * 5", 15.0f, Float.class);
@@ -569,7 +567,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testStrings() {
void strings() {
evaluate("'abc' == 'abc'", true, Boolean.class);
evaluate("'abc' == 'def'", false, Boolean.class);
evaluate("'abc' != 'abc'", false, Boolean.class);
@@ -577,7 +575,20 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testLongs() {
void stringRepeat() {
evaluate("'abc' * 0", "", String.class);
evaluate("'abc' * 1", "abc", String.class);
evaluate("'abc' * 2", "abcabc", String.class);
Expression expr = parser.parseExpression("'a' * 256");
assertThat(expr.getValue(context, String.class)).hasSize(256);
// 4 is the position of the '*' (repeat operator)
evaluateAndCheckError("'a' * 257", String.class, MAX_REPEATED_TEXT_SIZE_EXCEEDED, 4);
}
@Test
void longs() {
evaluate("3L == 4L", false, Boolean.class);
evaluate("3L == 3L", true, Boolean.class);
evaluate("3L != 4L", true, Boolean.class);
@@ -588,7 +599,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void testBigIntegers() {
void bigIntegers() {
evaluate("3 + new java.math.BigInteger('5')", new BigInteger("8"), BigInteger.class);
evaluate("3 - new java.math.BigInteger('5')", new BigInteger("-2"), BigInteger.class);
evaluate("3 * new java.math.BigInteger('5')", new BigInteger("15"), BigInteger.class);
@@ -619,7 +630,7 @@ class OperatorTests extends AbstractExpressionTests {
}
public static class BaseComparable implements Comparable<BaseComparable> {
static class BaseComparable implements Comparable<BaseComparable> {
@Override
public int compareTo(BaseComparable other) {
@@ -16,6 +16,8 @@
package org.springframework.expression.spel;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.expression.spel.standard.SpelExpression;
@@ -37,413 +39,517 @@ class ParsingTests {
private final SpelExpressionParser parser = new SpelExpressionParser();
// literals
@Test
void literalBoolean01() {
parseCheck("false");
@Nested
class Miscellaneous {
@Test
void literalNull() {
parseCheck("null");
}
@Test
void literalDate01() {
parseCheck("date('1974/08/24')");
}
@Test
void literalDate02() {
parseCheck("date('19740824T131030','yyyyMMddTHHmmss')");
}
@Test
void mixedOperators() {
parseCheck("true and 5>3", "(true and (5 > 3))");
}
@Test
void assignmentToVariables() {
parseCheck("#var1='value1'");
}
@Test
void collectionProcessorsCountStringArray() {
parseCheck("new String[] {'abc','def','xyz'}.count()");
}
@Test
void collectionProcessorsCountIntArray() {
parseCheck("new int[] {1,2,3}.count()");
}
@Test
void collectionProcessorsMax() {
parseCheck("new int[] {1,2,3}.max()");
}
@Test
void collectionProcessorsMin() {
parseCheck("new int[] {1,2,3}.min()");
}
@Test
void collectionProcessorsAverage() {
parseCheck("new int[] {1,2,3}.average()");
}
@Test
void collectionProcessorsSort() {
parseCheck("new int[] {3,2,1}.sort()");
}
@Test
void collectionProcessorsNonNull() {
parseCheck("{'a','b',null,'d',null}.nonNull()");
}
@Test
void collectionProcessorsDistinct() {
parseCheck("{'a','b','a','d','e'}.distinct()");
}
@Disabled("Unsupported syntax/feature")
@Test
void lambdaMax() {
parseCheck("(#max = {|x,y| $x > $y ? $x : $y }; #max(5,25))",
"(#max={|x,y| ($x > $y) ? $x : $y };#max(5,25))");
}
@Disabled("Unsupported syntax/feature")
@Test
void lambdaFactorial() {
parseCheck("(#fact = {|n| $n <= 1 ? 1 : $n * #fact($n-1) }; #fact(5))",
"(#fact={|n| ($n <= 1) ? 1 : ($n * #fact(($n - 1))) };#fact(5))");
}
@Disabled("Unsupported syntax/feature")
@Test
void projection() {
parseCheck("{1,2,3,4,5,6,7,8,9,10}.!{#isEven()}");
}
@Disabled("Unsupported syntax/feature")
@Test
void selection() {
parseCheck("{1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'}",
"{1,2,3,4,5,6,7,8,9,10}.?{(#isEven(#this) == 'y')}");
}
@Disabled("Unsupported syntax/feature")
@Test
void selectionFirst() {
parseCheck("{1,2,3,4,5,6,7,8,9,10}.^{#isEven(#this) == 'y'}",
"{1,2,3,4,5,6,7,8,9,10}.^{(#isEven(#this) == 'y')}");
}
@Disabled("Unsupported syntax/feature")
@Test
void selectionLast() {
parseCheck("{1,2,3,4,5,6,7,8,9,10}.${#isEven(#this) == 'y'}",
"{1,2,3,4,5,6,7,8,9,10}.${(#isEven(#this) == 'y')}");
}
}
@Test
void literalLong01() {
parseCheck("37L", "37");
@Nested
class LiteralBooleans {
@Test
void literalBooleanFalse() {
parseCheck("false");
}
@Test
void literalBooleanTrue() {
parseCheck("true");
}
@Test
void literalBooleanNotTrue() {
parseCheck("!true");
parseCheck("not true", "!true");
}
}
@Test
void literalBoolean02() {
parseCheck("true");
@Nested
class LiteralNumbers {
@Test
void literalLong() {
parseCheck("37L", "37");
}
@Test
void literalIntegers() {
parseCheck("1");
parseCheck("1415");
}
@Test
void literalReal() {
parseCheck("6.0221415E+23", "6.0221415E23");
}
@Test
void literalHex() {
parseCheck("0x7FFFFFFF", "2147483647");
}
}
@Test
void literalBoolean03() {
parseCheck("!true");
@Nested
class LiteralStrings {
@Test
void insideSingleQuotes() {
parseCheck("'hello'");
parseCheck("'hello world'");
}
@Test
void insideDoubleQuotes() {
parseCheck("\"hello\"", "'hello'");
parseCheck("\"hello world\"", "'hello world'");
}
@Test
void singleQuotesInsideSingleQuotes() {
parseCheck("'Tony''s Pizza'");
parseCheck("'big ''''pizza'''' parlor'");
}
@Test
void doubleQuotesInsideDoubleQuotes() {
parseCheck("\"big \"\"pizza\"\" parlor\"", "'big \"pizza\" parlor'");
parseCheck("\"big \"\"\"\"pizza\"\"\"\" parlor\"", "'big \"\"pizza\"\" parlor'");
}
@Test
void singleQuotesInsideDoubleQuotes() {
parseCheck("\"Tony's Pizza\"", "'Tony''s Pizza'");
parseCheck("\"big ''pizza'' parlor\"", "'big ''''pizza'''' parlor'");
}
@Test
void doubleQuotesInsideSingleQuotes() {
parseCheck("'big \"pizza\" parlor'");
parseCheck("'two double \"\" quotes'");
}
@Test
void inCompoundExpressions() {
parseCheck("'123''4' == '123''4'", "('123''4' == '123''4')");
parseCheck("('123''4'=='123''4')", "('123''4' == '123''4')");
parseCheck("\"123\"\"4\" == \"123\"\"4\"", "('123\"4' == '123\"4')");
}
}
@Test
void literalInteger01() {
parseCheck("1");
@Nested
class BooleanOperators {
@Test
void booleanOperatorsOr01() {
parseCheck("false or false", "(false or false)");
}
@Test
void booleanOperatorsOr02() {
parseCheck("false or true", "(false or true)");
}
@Test
void booleanOperatorsOr03() {
parseCheck("true or false", "(true or false)");
}
@Test
void booleanOperatorsOr04() {
parseCheck("true or false", "(true or false)");
}
@Test
void booleanOperatorsMix() {
parseCheck("false or true and false", "(false or (true and false))");
}
}
@Test
void literalInteger02() {
parseCheck("1415");
@Nested
class RelationalOperators {
@Test
void relOperatorsGT() {
parseCheck("3>6", "(3 > 6)");
}
@Test
void relOperatorsLT() {
parseCheck("3<6", "(3 < 6)");
}
@Test
void relOperatorsLE() {
parseCheck("3<=6", "(3 <= 6)");
}
@Test
void relOperatorsGE01() {
parseCheck("3>=6", "(3 >= 6)");
}
@Test
void relOperatorsGE02() {
parseCheck("3>=3", "(3 >= 3)");
}
@Disabled("Unsupported syntax/feature")
@Test
void relOperatorsIn() {
parseCheck("3 in {1,2,3,4,5}", "(3 in {1,2,3,4,5})");
}
@Test
void relOperatorsBetweenNumbers() {
parseCheck("1 between {1, 5}", "(1 between {1,5})");
}
@Test
void relOperatorsBetweenStrings() {
parseCheck("'efg' between {'abc', 'xyz'}", "('efg' between {'abc','xyz'})");
}
@Test
void relOperatorsInstanceOfInt() {
parseCheck("'xyz' instanceof int", "('xyz' instanceof int)");
}
@Test
void relOperatorsInstanceOfList() {
parseCheck("{1, 2, 3, 4, 5} instanceof List", "({1,2,3,4,5} instanceof List)");
}
@Test
void relOperatorsMatches() {
parseCheck("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "('5.0067' matches '^-?\\d+(\\.\\d{2})?$')");
parseCheck("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "('5.00' matches '^-?\\d+(\\.\\d{2})?$')");
}
}
@Test
void literalString01() {
parseCheck("'hello'");
@Nested
class MathematicalOperators {
@Test
void mathOperatorsAddIntegers() {
parseCheck("2+4", "(2 + 4)");
}
@Test
void mathOperatorsAddStrings() {
parseCheck("'a'+'b'", "('a' + 'b')");
}
@Test
void mathOperatorsAddMultipleStrings() {
parseCheck("'hello'+' '+'world'", "(('hello' + ' ') + 'world')");
}
@Test
void mathOperatorsSubtract() {
parseCheck("5-4", "(5 - 4)");
}
@Test
void mathOperatorsMultiply() {
parseCheck("7*4", "(7 * 4)");
}
@Test
void mathOperatorsDivide() {
parseCheck("8/4", "(8 / 4)");
}
@Test
void mathOperatorModulus() {
parseCheck("7 % 4", "(7 % 4)");
}
}
@Test
void literalString02() {
parseCheck("'joe bloggs'");
@Nested
class References {
@Test
void references() {
parseCheck("@foo");
parseCheck("@'foo.bar'");
parseCheck("@\"foo.bar.goo\"" , "@'foo.bar.goo'");
parseCheck("@$$foo");
}
}
@Test
void literalString03() {
parseCheck("'Tony''s Pizza'", "'Tony's Pizza'");
@Nested
class Properties {
@Test
void propertiesSingle() {
parseCheck("name");
}
@Test
void propertiesDouble() {
parseCheck("placeofbirth.CitY");
}
@Test
void propertiesMultiple() {
parseCheck("a.b.c.d.e");
}
}
@Test
void literalReal01() {
parseCheck("6.0221415E+23", "6.0221415E23");
@Nested
class InlineCollections {
@Test
void inlineListOfIntegers() {
parseCheck("{1,2,3,4}");
parseCheck("{1, 2, 3, 4, 5}", "{1,2,3,4,5}");
}
@Test
void inlineListOfStrings() {
parseCheck("{'abc','xyz'}", "{'abc','xyz'}");
parseCheck("{\"abc\", 'xyz'}", "{'abc','xyz'}");
}
@Test
void inlineMapStringToObject() {
parseCheck("{'key1':'Value 1','today':DateTime.Today}");
}
@Test
void inlineMapIntegerToString() {
parseCheck("{1:'January',2:'February',3:'March'}");
}
}
@Test
void literalHex01() {
parseCheck("0x7FFFFFFF", "2147483647");
@Nested
class MethodsConstructorsAndArrays {
@Test
void methods() {
parseCheck("echo()");
parseCheck("echo(12)");
parseCheck("echo(name)");
parseCheck("echo('Jane')");
parseCheck("echo('Jane',32)");
parseCheck("echo('Jane', 32)", "echo('Jane',32)");
parseCheck("age.doubleItAndAdd(12)");
}
@Test
void constructorWithNoArguments() {
parseCheck("new Foo()");
parseCheck("new example.Foo()");
}
@Test
void constructorWithOneArgument() {
parseCheck("new String('hello')");
parseCheck("new String( 'hello' )", "new String('hello')");
parseCheck("new String(\"hello\" )", "new String('hello')");
}
@Test
void constructorWithMultipleArguments() {
parseCheck("new example.Person('Jane',32,true)");
parseCheck("new example.Person('Jane', 32, true)", "new example.Person('Jane',32,true)");
parseCheck("new example.Person('Jane', 2 * 16, true)", "new example.Person('Jane',(2 * 16),true)");
}
@Test
void arrayConstructionWithOneDimensionalReferenceType() {
parseCheck("new String[3]");
}
@Test
void arrayConstructionWithOneDimensionalFullyQualifiedReferenceType() {
parseCheck("new java.lang.String[3]");
}
@Test
void arrayConstructionWithOneDimensionalPrimitiveType() {
parseCheck("new int[3]");
}
@Test
void arrayConstructionWithMultiDimensionalReferenceType() {
parseCheck("new Float[3][4]");
}
@Test
void arrayConstructionWithMultiDimensionalPrimitiveType() {
parseCheck("new int[3][4]");
}
@Test
void arrayConstructionWithOneDimensionalReferenceTypeWithInitializer() {
parseCheck("new String[] {'abc','xyz'}");
parseCheck("new String[] {'abc', 'xyz'}", "new String[] {'abc','xyz'}");
}
@Test
void arrayConstructionWithOneDimensionalPrimitiveTypeWithInitializer() {
parseCheck("new int[] {1,2,3,4,5}");
parseCheck("new int[] {1, 2, 3, 4, 5}", "new int[] {1,2,3,4,5}");
}
}
@Test
void literalDate01() {
parseCheck("date('1974/08/24')");
@Nested
class VariablesAndFunctions {
@Test
void variables() {
parseCheck("#foo");
}
@Test
void functions() {
parseCheck("#fn(1,2,3)");
parseCheck("#fn('hello')");
}
}
@Test
void literalDate02() {
parseCheck("date('19740824T131030','yyyyMMddTHHmmss')");
@Nested
class ElvisAndTernaryOperators {
@Test
void elvis() {
parseCheck("3?:1", "(3 ?: 1)");
parseCheck("(2*3)?:1*10", "((2 * 3) ?: (1 * 10))");
parseCheck("((2*3)?:1)*10", "(((2 * 3) ?: 1) * 10)");
}
@Test
void ternary() {
parseCheck("1>2?3:4", "((1 > 2) ? 3 : 4)");
parseCheck("(a ? 1 : 0) * 10", "((a ? 1 : 0) * 10)");
parseCheck("(a?1:0)*10", "((a ? 1 : 0) * 10)");
parseCheck("(4 % 2 == 0 ? 1 : 0) * 10", "((((4 % 2) == 0) ? 1 : 0) * 10)");
parseCheck("((4 % 2 == 0) ? 1 : 0) * 10", "((((4 % 2) == 0) ? 1 : 0) * 10)");
parseCheck("{1}.#isEven(#this) == 'y'?'it is even':'it is odd'",
"(({1}.#isEven(#this) == 'y') ? 'it is even' : 'it is odd')");
}
}
@Test
void literalNull01() {
parseCheck("null");
@Nested
class TypeReferences {
@Test
void typeReferences01() {
parseCheck("T(java.lang.String)");
}
@Test
void typeReferences02() {
parseCheck("T(String)");
}
}
// boolean operators
@Test
void booleanOperatorsOr01() {
parseCheck("false or false", "(false or false)");
}
@Test
void booleanOperatorsOr02() {
parseCheck("false or true", "(false or true)");
}
@Test
void booleanOperatorsOr03() {
parseCheck("true or false", "(true or false)");
}
@Test
void booleanOperatorsOr04() {
parseCheck("true or false", "(true or false)");
}
@Test
void booleanOperatorsMix01() {
parseCheck("false or true and false", "(false or (true and false))");
}
// relational operators
@Test
void relOperatorsGT01() {
parseCheck("3>6", "(3 > 6)");
}
@Test
void relOperatorsLT01() {
parseCheck("3<6", "(3 < 6)");
}
@Test
void relOperatorsLE01() {
parseCheck("3<=6", "(3 <= 6)");
}
@Test
void relOperatorsGE01() {
parseCheck("3>=6", "(3 >= 6)");
}
@Test
void relOperatorsGE02() {
parseCheck("3>=3", "(3 >= 3)");
}
@Test
void elvis() {
parseCheck("3?:1", "(3 ?: 1)");
parseCheck("(2*3)?:1*10", "((2 * 3) ?: (1 * 10))");
parseCheck("((2*3)?:1)*10", "(((2 * 3) ?: 1) * 10)");
}
// void relOperatorsIn01() {
// parseCheck("3 in {1,2,3,4,5}", "(3 in {1,2,3,4,5})");
// }
//
// void relOperatorsBetween01() {
// parseCheck("1 between {1, 5}", "(1 between {1,5})");
// }
// void relOperatorsBetween02() {
// parseCheck("'efg' between {'abc', 'xyz'}", "('efg' between {'abc','xyz'})");
// }// true
@Test
void relOperatorsIs01() {
parseCheck("'xyz' instanceof int", "('xyz' instanceof int)");
}// false
// void relOperatorsIs02() {
// parseCheck("{1, 2, 3, 4, 5} instanceof List", "({1,2,3,4,5} instanceof List)");
// }// true
@Test
void relOperatorsMatches01() {
parseCheck("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "('5.0067' matches '^-?\\d+(\\.\\d{2})?$')");
}// false
@Test
void relOperatorsMatches02() {
parseCheck("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "('5.00' matches '^-?\\d+(\\.\\d{2})?$')");
}// true
// mathematical operators
@Test
void mathOperatorsAdd01() {
parseCheck("2+4", "(2 + 4)");
}
@Test
void mathOperatorsAdd02() {
parseCheck("'a'+'b'", "('a' + 'b')");
}
@Test
void mathOperatorsAdd03() {
parseCheck("'hello'+' '+'world'", "(('hello' + ' ') + 'world')");
}
@Test
void mathOperatorsSubtract01() {
parseCheck("5-4", "(5 - 4)");
}
@Test
void mathOperatorsMultiply01() {
parseCheck("7*4", "(7 * 4)");
}
@Test
void mathOperatorsDivide01() {
parseCheck("8/4", "(8 / 4)");
}
@Test
void mathOperatorModulus01() {
parseCheck("7 % 4", "(7 % 4)");
}
// mixed operators
@Test
void mixedOperators01() {
parseCheck("true and 5>3", "(true and (5 > 3))");
}
// collection processors
// void collectionProcessorsCount01() {
// parseCheck("new String[] {'abc','def','xyz'}.count()");
// }
// void collectionProcessorsCount02() {
// parseCheck("new int[] {1,2,3}.count()");
// }
//
// void collectionProcessorsMax01() {
// parseCheck("new int[] {1,2,3}.max()");
// }
//
// void collectionProcessorsMin01() {
// parseCheck("new int[] {1,2,3}.min()");
// }
//
// void collectionProcessorsAverage01() {
// parseCheck("new int[] {1,2,3}.average()");
// }
//
// void collectionProcessorsSort01() {
// parseCheck("new int[] {3,2,1}.sort()");
// }
//
// void collectionProcessorsNonNull01() {
// parseCheck("{'a','b',null,'d',null}.nonNull()");
// }
//
// void collectionProcessorsDistinct01() {
// parseCheck("{'a','b','a','d','e'}.distinct()");
// }
// references
@Test
void references01() {
parseCheck("@foo");
parseCheck("@'foo.bar'");
parseCheck("@\"foo.bar.goo\"" , "@'foo.bar.goo'");
}
@Test
void references03() {
parseCheck("@$$foo");
}
// properties
@Test
void properties01() {
parseCheck("name");
}
@Test
void properties02() {
parseCheck("placeofbirth.CitY");
}
@Test
void properties03() {
parseCheck("a.b.c.d.e");
}
// inline list creation
@Test
void inlineListCreation01() {
parseCheck("{1, 2, 3, 4, 5}", "{1,2,3,4,5}");
}
@Test
void inlineListCreation02() {
parseCheck("{'abc','xyz'}", "{'abc','xyz'}");
}
// inline map creation
@Test
void inlineMapCreation01() {
parseCheck("{'key1':'Value 1','today':DateTime.Today}");
}
@Test
void inlineMapCreation02() {
parseCheck("{1:'January',2:'February',3:'March'}");
}
// methods
@Test
void methods01() {
parseCheck("echo(12)");
}
@Test
void methods02() {
parseCheck("echo(name)");
}
@Test
void methods03() {
parseCheck("age.doubleItAndAdd(12)");
}
// constructors
@Test
void constructors01() {
parseCheck("new String('hello')");
}
// void constructors02() {
// parseCheck("new String[3]");
// }
// array construction
// void arrayConstruction01() {
// parseCheck("new int[] {1, 2, 3, 4, 5}", "new int[] {1,2,3,4,5}");
// }
//
// void arrayConstruction02() {
// parseCheck("new String[] {'abc','xyz'}", "new String[] {'abc','xyz'}");
// }
// variables and functions
@Test
void variables01() {
parseCheck("#foo");
}
@Test
void functions01() {
parseCheck("#fn(1,2,3)");
}
@Test
void functions02() {
parseCheck("#fn('hello')");
}
// projections and selections
// void projections01() {
// parseCheck("{1,2,3,4,5,6,7,8,9,10}.!{#isEven()}");
// }
// void selections01() {
// parseCheck("{1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'}",
// "{1,2,3,4,5,6,7,8,9,10}.?{(#isEven(#this) == 'y')}");
// }
// void selectionsFirst01() {
// parseCheck("{1,2,3,4,5,6,7,8,9,10}.^{#isEven(#this) == 'y'}",
// "{1,2,3,4,5,6,7,8,9,10}.^{(#isEven(#this) == 'y')}");
// }
// void selectionsLast01() {
// parseCheck("{1,2,3,4,5,6,7,8,9,10}.${#isEven(#this) == 'y'}",
// "{1,2,3,4,5,6,7,8,9,10}.${(#isEven(#this) == 'y')}");
// }
// assignment
@Test
void assignmentToVariables01() {
parseCheck("#var1='value1'");
}
// ternary operator
@Test
void ternaryOperator01() {
parseCheck("1>2?3:4", "((1 > 2) ? 3 : 4)");
parseCheck("(a ? 1 : 0) * 10", "((a ? 1 : 0) * 10)");
parseCheck("(a?1:0)*10", "((a ? 1 : 0) * 10)");
parseCheck("(4 % 2 == 0 ? 1 : 0) * 10", "((((4 % 2) == 0) ? 1 : 0) * 10)");
parseCheck("((4 % 2 == 0) ? 1 : 0) * 10", "((((4 % 2) == 0) ? 1 : 0) * 10)");
}
@Test
void ternaryOperator02() {
parseCheck("{1}.#isEven(#this) == 'y'?'it is even':'it is odd'",
"(({1}.#isEven(#this) == 'y') ? 'it is even' : 'it is odd')");
}
//
// void lambdaMax() {
// parseCheck("(#max = {|x,y| $x > $y ? $x : $y }; #max(5,25))", "(#max={|x,y| ($x > $y) ? $x : $y };#max(5,25))");
// }
//
// void lambdaFactorial() {
// parseCheck("(#fact = {|n| $n <= 1 ? 1 : $n * #fact($n-1) }; #fact(5))",
// "(#fact={|n| ($n <= 1) ? 1 : ($n * #fact(($n - 1))) };#fact(5))");
// } // 120
// Type references
@Test
void typeReferences01() {
parseCheck("T(java.lang.String)");
}
@Test
void typeReferences02() {
parseCheck("T(String)");
}
@Test
void inlineList1() {
parseCheck("{1,2,3,4}");
}
/**
* Parse the supplied expression and then create a string representation of the resultant AST, it should be the same
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -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.
@@ -209,7 +209,7 @@ class SpelReproTests extends AbstractExpressionTests {
checkTemplateParsingError("abc${ } }", "No expression defined within delimiter '${}' at character 3");
checkTemplateParsingError("abc$[ } ]", DOLLARSQUARE_TEMPLATE_PARSER_CONTEXT, "Found closing '}' at position 6 without an opening '{'");
checkTemplateParsing("abc ${\"def''g}hi\"} jkl", "abc def'g}hi jkl");
checkTemplateParsing("abc ${\"def''g}hi\"} jkl", "abc def''g}hi jkl");
checkTemplateParsing("abc ${'def''g}hi'} jkl", "abc def'g}hi jkl");
checkTemplateParsing("}", "}");
checkTemplateParsing("${'hello'} world", "hello world");
@@ -1817,7 +1817,6 @@ class SpelReproTests extends AbstractExpressionTests {
static class CCC {
public boolean method(Object o) {
System.out.println(o);
return false;
}
}
@@ -1889,7 +1888,6 @@ class SpelReproTests extends AbstractExpressionTests {
static class Foo2 {
public void execute(String str) {
System.out.println("Value: " + str);
}
}
@@ -1964,7 +1962,6 @@ class SpelReproTests extends AbstractExpressionTests {
public static class ReflectionUtil<T extends Number> {
public Object methodToCall(T param) {
System.out.println(param + " " + param.getClass());
return "Object methodToCall(T param)";
}
@@ -0,0 +1,34 @@
/*
* Copyright 2002-2022 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.expression.spel;
import org.junit.platform.suite.api.IncludeClassNamePatterns;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
/**
* JUnit Platform based test suite for tests in the spring-expression module.
*
* <p><strong>This suite is only intended to be used manually within an IDE.</strong>
*
* @author Sam Brannen
*/
@Suite
@SelectPackages("org.springframework.expression.spel")
@IncludeClassNamePatterns(".*Tests?$")
class SpringExpressionTestSuite {
}
@@ -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.
@@ -19,13 +19,17 @@ package org.springframework.expression.spel.support;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.MethodExecutor;
import org.springframework.expression.MethodResolver;
import org.springframework.expression.ParseException;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
@@ -364,6 +368,20 @@ public class ReflectionHelperTests extends AbstractExpressionTests {
field.write(ctx, tester, "field", null));
}
@Test
void reflectiveMethodResolverForJdkProxies() throws Exception {
Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { Runnable.class }, (p, m, args) -> null);
MethodResolver resolver = new ReflectiveMethodResolver();
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
MethodExecutor bogus = resolver.resolve(evaluationContext, proxy, "bogus", Collections.emptyList());
assertThat(bogus).as("MethodExecutor for bogus()").isNull();
MethodExecutor toString = resolver.resolve(evaluationContext, proxy, "toString", Collections.emptyList());
assertThat(toString).as("MethodExecutor for toString()").isNotNull();
MethodExecutor hashCode = resolver.resolve(evaluationContext, proxy, "hashCode", Collections.emptyList());
assertThat(hashCode).as("MethodExecutor for hashCode()").isNotNull();
}
/**
* Used to validate the match returned from a compareArguments call.
@@ -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.
@@ -48,31 +48,42 @@ import org.springframework.util.StringUtils;
/**
* {@link RowMapper} implementation that converts a row into a new instance
* of the specified mapped target class. The mapped target class must be a
* top-level class and it must have a default or no-arg constructor.
* top-level class or {@code static} nested class, and it must have a default or
* no-arg constructor.
*
* <p>Column values are mapped based on matching the column name as obtained from result set
* meta-data to public setters for the corresponding properties. The names are matched either
* directly or by transforming a name separating the parts with underscores to the same name
* using "camel" case.
* <p>Column values are mapped based on matching the column name (as obtained from
* result set meta-data) to public setters in the target class for the corresponding
* properties. The names are matched either directly or by transforming a name
* separating the parts with underscores to the same name using "camel" case.
*
* <p>Mapping is provided for fields in the target class for many common types, e.g.:
* String, boolean, Boolean, byte, Byte, short, Short, int, Integer, long, Long,
* float, Float, double, Double, BigDecimal, {@code java.util.Date}, etc.
* <p>Mapping is provided for properties in the target class for many common types &mdash;
* for example: String, boolean, Boolean, byte, Byte, short, Short, int, Integer,
* long, Long, float, Float, double, Double, BigDecimal, {@code java.util.Date}, etc.
*
* <p>To facilitate mapping between columns and fields that don't have matching names,
* try using column aliases in the SQL statement like "select fname as first_name from customer".
* <p>To facilitate mapping between columns and properties that don't have matching
* names, try using column aliases in the SQL statement like
* {@code "select fname as first_name from customer"}, where {@code first_name}
* can be mapped to a {@code setFirstName(String)} method in the target class.
*
* <p>For 'null' values read from the database, we will attempt to call the setter, but in the case of
* Java primitives, this causes a TypeMismatchException. This class can be configured (using the
* primitivesDefaultedForNullValue property) to trap this exception and use the primitives default value.
* Be aware that if you use the values from the generated bean to update the database the primitive value
* will have been set to the primitive's default value instead of null.
* <p>For a {@code NULL} value read from the database, an attempt will be made to
* call the corresponding setter method with {@code null}, but in the case of
* Java primitives this will result in a {@link TypeMismatchException} by default.
* To ignore {@code NULL} database values for all primitive properties in the
* target class, set the {@code primitivesDefaultedForNullValue} flag to
* {@code true}. See {@link #setPrimitivesDefaultedForNullValue(boolean)} for
* details.
*
* <p>Please note that this class is designed to provide convenience rather than high performance.
* For best performance, consider using a custom {@link RowMapper} implementation.
* <p>If you need to map to a target class which has a <em>data class</em> constructor
* &mdash; for example, a Java {@code record} or a Kotlin {@code data} class &mdash;
* use {@link DataClassRowMapper} instead.
*
* <p>Please note that this class is designed to provide convenience rather than
* high performance. For best performance, consider using a custom {@code RowMapper}
* implementation.
*
* @author Thomas Risberg
* @author Juergen Hoeller
* @author Sam Brannen
* @since 2.5
* @param <T> the result type
* @see DataClassRowMapper
@@ -89,20 +100,24 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
/** Whether we're strictly validating. */
private boolean checkFullyPopulated = false;
/** Whether we're defaulting primitives when mapping a null value. */
/**
* Whether {@code NULL} database values should be ignored for primitive
* properties in the target class.
* @see #setPrimitivesDefaultedForNullValue(boolean)
*/
private boolean primitivesDefaultedForNullValue = false;
/** ConversionService for binding JDBC values to bean properties. */
@Nullable
private ConversionService conversionService = DefaultConversionService.getSharedInstance();
/** Map of the fields we provide mapping for. */
/** Map of the properties we provide mapping for. */
@Nullable
private Map<String, PropertyDescriptor> mappedFields;
private Map<String, PropertyDescriptor> mappedProperties;
/** Set of bean properties we provide mapping for. */
/** Set of bean property names we provide mapping for. */
@Nullable
private Set<String> mappedProperties;
private Set<String> mappedPropertyNames;
/**
@@ -126,7 +141,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
* Create a new {@code BeanPropertyRowMapper}.
* @param mappedClass the class that each row should be mapped to
* @param checkFullyPopulated whether we're strictly validating that
* all bean properties have been mapped from corresponding database fields
* all bean properties have been mapped from corresponding database columns
*/
public BeanPropertyRowMapper(Class<T> mappedClass, boolean checkFullyPopulated) {
initialize(mappedClass);
@@ -159,7 +174,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
/**
* Set whether we're strictly validating that all bean properties have been mapped
* from corresponding database fields.
* from corresponding database columns.
* <p>Default is {@code false}, accepting unpopulated properties in the target bean.
*/
public void setCheckFullyPopulated(boolean checkFullyPopulated) {
@@ -168,24 +183,33 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
/**
* Return whether we're strictly validating that all bean properties have been
* mapped from corresponding database fields.
* mapped from corresponding database columns.
*/
public boolean isCheckFullyPopulated() {
return this.checkFullyPopulated;
}
/**
* Set whether we're defaulting Java primitives in the case of mapping a null value
* from corresponding database fields.
* <p>Default is {@code false}, throwing an exception when nulls are mapped to Java primitives.
* Set whether a {@code NULL} database column value should be ignored when
* mapping to a corresponding primitive property in the target class.
* <p>Default is {@code false}, throwing an exception when nulls are mapped
* to Java primitives.
* <p>If this flag is set to {@code true} and you use an <em>ignored</em>
* primitive property value from the mapped bean to update the database, the
* value in the database will be changed from {@code NULL} to the current value
* of that primitive property. That value may be the property's initial value
* (potentially Java's default value for the respective primitive type), or
* it may be some other value set for the property in the default constructor
* (or initialization block) or as a side effect of setting some other property
* in the mapped bean.
*/
public void setPrimitivesDefaultedForNullValue(boolean primitivesDefaultedForNullValue) {
this.primitivesDefaultedForNullValue = primitivesDefaultedForNullValue;
}
/**
* Return whether we're defaulting Java primitives in the case of mapping a null value
* from corresponding database fields.
* Get the value of the {@code primitivesDefaultedForNullValue} flag.
* @see #setPrimitivesDefaultedForNullValue(boolean)
*/
public boolean isPrimitivesDefaultedForNullValue() {
return this.primitivesDefaultedForNullValue;
@@ -220,31 +244,31 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
*/
protected void initialize(Class<T> mappedClass) {
this.mappedClass = mappedClass;
this.mappedFields = new HashMap<>();
this.mappedProperties = new HashSet<>();
this.mappedProperties = new HashMap<>();
this.mappedPropertyNames = new HashSet<>();
for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(mappedClass)) {
if (pd.getWriteMethod() != null) {
String lowerCaseName = lowerCaseName(pd.getName());
this.mappedFields.put(lowerCaseName, pd);
this.mappedProperties.put(lowerCaseName, pd);
String underscoreName = underscoreName(pd.getName());
if (!lowerCaseName.equals(underscoreName)) {
this.mappedFields.put(underscoreName, pd);
this.mappedProperties.put(underscoreName, pd);
}
this.mappedProperties.add(pd.getName());
this.mappedPropertyNames.add(pd.getName());
}
}
}
/**
* Remove the specified property from the mapped fields.
* Remove the specified property from the mapped properties.
* @param propertyName the property name (as used by property descriptors)
* @since 5.3.9
*/
protected void suppressProperty(String propertyName) {
if (this.mappedFields != null) {
this.mappedFields.remove(lowerCaseName(propertyName));
this.mappedFields.remove(underscoreName(propertyName));
if (this.mappedProperties != null) {
this.mappedProperties.remove(lowerCaseName(propertyName));
this.mappedProperties.remove(underscoreName(propertyName));
}
}
@@ -306,8 +330,8 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
for (int index = 1; index <= columnCount; index++) {
String column = JdbcUtils.lookupColumnName(rsmd, index);
String field = lowerCaseName(StringUtils.delete(column, " "));
PropertyDescriptor pd = (this.mappedFields != null ? this.mappedFields.get(field) : null);
String property = lowerCaseName(StringUtils.delete(column, " "));
PropertyDescriptor pd = (this.mappedProperties != null ? this.mappedProperties.get(property) : null);
if (pd != null) {
try {
Object value = getColumnValue(rs, index, pd);
@@ -321,11 +345,11 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
catch (TypeMismatchException ex) {
if (value == null && this.primitivesDefaultedForNullValue) {
if (logger.isDebugEnabled()) {
logger.debug("Intercepted TypeMismatchException for row " + rowNumber +
" and column '" + column + "' with null value when setting property '" +
pd.getName() + "' of type '" +
ClassUtils.getQualifiedName(pd.getPropertyType()) +
"' on object: " + mappedObject, ex);
String propertyType = ClassUtils.getQualifiedName(pd.getPropertyType());
logger.debug(String.format(
"Ignoring intercepted TypeMismatchException for row %d and column '%s' " +
"with null value when setting property '%s' of type '%s' on object: %s",
rowNumber, column, pd.getName(), propertyType, mappedObject), ex);
}
}
else {
@@ -343,9 +367,9 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
}
}
if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) {
throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all fields " +
"necessary to populate object of " + this.mappedClass + ": " + this.mappedProperties);
if (populatedProperties != null && !populatedProperties.equals(this.mappedPropertyNames)) {
throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all properties " +
"necessary to populate object of " + this.mappedClass + ": " + this.mappedPropertyNames);
}
return mappedObject;
@@ -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.
@@ -31,15 +31,30 @@ import org.springframework.util.Assert;
/**
* {@link RowMapper} implementation that converts a row into a new instance
* of the specified mapped target class. The mapped target class must be a
* top-level class and may either expose a data class constructor with named
* parameters corresponding to column names or classic bean property setters
* (or even a combination of both).
* top-level class or {@code static} nested class, and it may expose either a
* <em>data class</em> constructor with named parameters corresponding to column
* names or classic bean property setter methods with property names corresponding
* to column names (or even a combination of both).
*
* <p>The term "data class" applies to Java <em>records</em>, Kotlin <em>data
* classes</em>, and any class which has a constructor with named parameters
* that are intended to be mapped to corresponding column names.
*
* <p>When combining a data class constructor with setter methods, any property
* mapped successfully via a constructor argument will not be mapped additionally
* via a corresponding setter method. This means that constructor arguments take
* precedence over property setter methods.
*
* <p>Note that this class extends {@link BeanPropertyRowMapper} and can
* therefore serve as a common choice for any mapped target class, flexibly
* adapting to constructor style versus setter methods in the mapped class.
*
* <p>Please note that this class is designed to provide convenience rather than
* high performance. For best performance, consider using a custom {@code RowMapper}
* implementation.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 5.3
* @param <T> the result type
*/
@@ -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.
@@ -24,21 +24,21 @@ import org.springframework.lang.Nullable;
/**
* An interface used by {@link JdbcTemplate} for mapping rows of a
* {@link java.sql.ResultSet} on a per-row basis. Implementations of this
* interface perform the actual work of mapping each row to a result object,
* interface perform the actual work of mapping each row to a result object
* but don't need to worry about exception handling.
* {@link java.sql.SQLException SQLExceptions} will be caught and handled
* by the calling JdbcTemplate.
* by the calling {@code JdbcTemplate}.
*
* <p>Typically used either for {@link JdbcTemplate}'s query methods
* or for out parameters of stored procedures. RowMapper objects are
* <p>Typically used either for {@code JdbcTemplate}'s query methods or for
* {@code out} parameters of stored procedures. {@code RowMapper} objects are
* typically stateless and thus reusable; they are an ideal choice for
* implementing row-mapping logic in a single place.
*
* <p>Alternatively, consider subclassing
* {@link org.springframework.jdbc.object.MappingSqlQuery} from the
* {@code jdbc.object} package: Instead of working with separate
* JdbcTemplate and RowMapper objects, you can build executable query
* objects (containing row-mapping logic) in that style.
* {@code jdbc.object} package: instead of working with separate
* {@code JdbcTemplate} and {@code RowMapper} objects, you can build executable
* query objects (containing row-mapping logic) in that style.
*
* @author Thomas Risberg
* @author Juergen Hoeller
@@ -52,13 +52,13 @@ import org.springframework.lang.Nullable;
public interface RowMapper<T> {
/**
* Implementations must implement this method to map each row of data
* in the ResultSet. This method should not call {@code next()} on
* the ResultSet; it is only supposed to map values of the current row.
* @param rs the ResultSet to map (pre-initialized for the current row)
* Implementations must implement this method to map each row of data in the
* {@code ResultSet}. This method should not call {@code next()} on the
* {@code ResultSet}; it is only supposed to map values of the current row.
* @param rs the {@code ResultSet} to map (pre-initialized for the current row)
* @param rowNum the number of the current row
* @return the result object for the current row (may be {@code null})
* @throws SQLException if an SQLException is encountered getting
* @throws SQLException if an SQLException is encountered while getting
* column values (that is, there's no need to catch SQLException)
*/
@Nullable
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2022 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.
@@ -30,15 +30,15 @@ import org.springframework.lang.Nullable;
/**
* Object to represent an SQL BLOB/CLOB value parameter. BLOBs can either be an
* InputStream or a byte array. CLOBs can be in the form of a Reader, InputStream
* InputStream or a byte array. CLOBs can be in the form of a Reader, InputStream,
* or String. Each CLOB/BLOB value will be stored together with its length.
* The type is based on which constructor is used. Objects of this class are
* immutable except for the LobCreator reference. Use them and discard them.
* The type is based on which constructor is used. Instances of this class are
* stateful and immutable: use them and discard them.
*
* <p>This class holds a reference to a LocCreator that must be closed after the
* update has completed. This is done via a call to the closeLobCreator method.
* All handling of the LobCreator is done by the framework classes that use it -
* no need to set or close the LobCreator for end users of this class.
* <p>This class holds a reference to a {@link LobCreator} that must be closed after
* the update has completed. This is done via a call to the {@link #cleanup()} method.
* All handling of the {@code LobCreator} is done by the framework classes that use it -
* no need to set or close the {@code LobCreator} for end users of this class.
*
* <p>A usage example:
*
@@ -72,8 +72,7 @@ public class SqlLobValue implements DisposableSqlTypeValue {
private final int length;
/**
* This contains a reference to the LobCreator - so we can close it
* once the update is done.
* Reference to the LobCreator - so we can close it once the update is done.
*/
private final LobCreator lobCreator;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 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.
@@ -69,10 +69,10 @@ public class SQLExceptionSubclassTranslator extends AbstractFallbackSQLException
if (ex instanceof SQLTransientConnectionException) {
return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex);
}
else if (ex instanceof SQLTransactionRollbackException) {
if (ex instanceof SQLTransactionRollbackException) {
return new ConcurrencyFailureException(buildMessage(task, sql, ex), ex);
}
else if (ex instanceof SQLTimeoutException) {
if (ex instanceof SQLTimeoutException) {
return new QueryTimeoutException(buildMessage(task, sql, ex), ex);
}
}
@@ -80,19 +80,19 @@ public class SQLExceptionSubclassTranslator extends AbstractFallbackSQLException
if (ex instanceof SQLNonTransientConnectionException) {
return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex);
}
else if (ex instanceof SQLDataException) {
if (ex instanceof SQLDataException) {
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
}
else if (ex instanceof SQLIntegrityConstraintViolationException) {
if (ex instanceof SQLIntegrityConstraintViolationException) {
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
}
else if (ex instanceof SQLInvalidAuthorizationSpecException) {
if (ex instanceof SQLInvalidAuthorizationSpecException) {
return new PermissionDeniedDataAccessException(buildMessage(task, sql, ex), ex);
}
else if (ex instanceof SQLSyntaxErrorException) {
if (ex instanceof SQLSyntaxErrorException) {
return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex);
}
else if (ex instanceof SQLFeatureNotSupportedException) {
if (ex instanceof SQLFeatureNotSupportedException) {
return new InvalidDataAccessApiUsageException(buildMessage(task, sql, ex), ex);
}
}
@@ -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.
@@ -16,8 +16,6 @@
package org.springframework.jdbc.core;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
@@ -44,12 +42,15 @@ import static org.assertj.core.api.Assertions.assertThatNoException;
*/
class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
private static final String SELECT_NULL_AS_AGE = "select null as age from people";
@Test
@SuppressWarnings({"unchecked", "rawtypes"})
void overridingDifferentClassDefinedForMapping() {
BeanPropertyRowMapper mapper = new BeanPropertyRowMapper(Person.class);
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() ->
mapper.setMappedClass(Long.class));
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
.isThrownBy(() -> mapper.setMappedClass(Long.class));
}
@Test
@@ -61,104 +62,106 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
@Test
void staticQueryWithRowMapper() throws Exception {
Mock mock = new Mock();
List<Person> result = mock.getJdbcTemplate().query(
Person person = mock.getJdbcTemplate().queryForObject(
"select name, age, birth_date, balance from people",
new BeanPropertyRowMapper<>(Person.class));
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
verifyPerson(person);
mock.verifyClosed();
}
@Test
void mappingWithInheritance() throws Exception {
Mock mock = new Mock();
List<ConcretePerson> result = mock.getJdbcTemplate().query(
ConcretePerson person = mock.getJdbcTemplate().queryForObject(
"select name, age, birth_date, balance from people",
new BeanPropertyRowMapper<>(ConcretePerson.class));
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
verifyPerson(person);
mock.verifyClosed();
}
@Test
void mappingWithNoUnpopulatedFieldsFound() throws Exception {
Mock mock = new Mock();
List<ConcretePerson> result = mock.getJdbcTemplate().query(
ConcretePerson person = mock.getJdbcTemplate().queryForObject(
"select name, age, birth_date, balance from people",
new BeanPropertyRowMapper<>(ConcretePerson.class, true));
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
verifyPerson(person);
mock.verifyClosed();
}
@Test
void mappingWithUnpopulatedFieldsNotChecked() throws Exception {
Mock mock = new Mock();
List<ExtendedPerson> result = mock.getJdbcTemplate().query(
ExtendedPerson person = mock.getJdbcTemplate().queryForObject(
"select name, age, birth_date, balance from people",
new BeanPropertyRowMapper<>(ExtendedPerson.class));
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
verifyPerson(person);
mock.verifyClosed();
}
@Test
void mappingWithUnpopulatedFieldsNotAccepted() throws Exception {
BeanPropertyRowMapper<ExtendedPerson> mapper = new BeanPropertyRowMapper<>(ExtendedPerson.class, true);
Mock mock = new Mock();
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() ->
mock.getJdbcTemplate().query("select name, age, birth_date, balance from people",
new BeanPropertyRowMapper<>(ExtendedPerson.class, true)));
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
.isThrownBy(() -> mock.getJdbcTemplate().query("select name, age, birth_date, balance from people", mapper));
}
@Test
void mappingNullValue() throws Exception {
BeanPropertyRowMapper<Person> mapper = new BeanPropertyRowMapper<>(Person.class);
Mock mock = new Mock(MockType.TWO);
assertThatExceptionOfType(TypeMismatchException.class).isThrownBy(() ->
mock.getJdbcTemplate().query("select name, null as age, birth_date, balance from people", mapper));
assertThatExceptionOfType(TypeMismatchException.class)
.isThrownBy(() -> mock.getJdbcTemplate().query(SELECT_NULL_AS_AGE, mapper));
}
@Test
void mappingNullValueWithPrimitivesDefaultedForNullValue() throws Exception {
BeanPropertyRowMapper<Person> mapper = new BeanPropertyRowMapper<>(Person.class);
mapper.setPrimitivesDefaultedForNullValue(true);
Mock mock = new Mock(MockType.TWO);
Person person = mock.getJdbcTemplate().queryForObject(SELECT_NULL_AS_AGE, mapper);
assertThat(person).extracting(Person::getAge).isEqualTo(42L);
mock.verifyClosed();
}
@Test
void queryWithSpaceInColumnNameAndLocalDateTime() throws Exception {
Mock mock = new Mock(MockType.THREE);
List<SpacePerson> result = mock.getJdbcTemplate().query(
SpacePerson person = mock.getJdbcTemplate().queryForObject(
"select last_name as \"Last Name\", age, birth_date, balance from people",
new BeanPropertyRowMapper<>(SpacePerson.class));
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
verifyPerson(person);
mock.verifyClosed();
}
@Test
void queryWithSpaceInColumnNameAndLocalDate() throws Exception {
Mock mock = new Mock(MockType.THREE);
List<DatePerson> result = mock.getJdbcTemplate().query(
DatePerson person = mock.getJdbcTemplate().queryForObject(
"select last_name as \"Last Name\", age, birth_date, balance from people",
new BeanPropertyRowMapper<>(DatePerson.class));
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
verifyPerson(person);
mock.verifyClosed();
}
@Test
void queryWithDirectNameMatchOnBirthDate() throws Exception {
Mock mock = new Mock(MockType.FOUR);
List<ConcretePerson> result = mock.getJdbcTemplate().query(
ConcretePerson person = mock.getJdbcTemplate().queryForObject(
"select name, age, birthdate, balance from people",
new BeanPropertyRowMapper<>(ConcretePerson.class));
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
verifyPerson(person);
mock.verifyClosed();
}
@Test
void queryWithUnderscoreInColumnNameAndPersonWithMultipleAdjacentUppercaseLettersInPropertyName() throws Exception {
Mock mock = new Mock();
List<EmailPerson> result = mock.getJdbcTemplate().query(
EmailPerson person = mock.getJdbcTemplate().queryForObject(
"select name, age, birth_date, balance, e_mail from people",
new BeanPropertyRowMapper<>(EmailPerson.class));
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
verifyPerson(person);
mock.verifyClosed();
}
@@ -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,9 +17,7 @@
package org.springframework.jdbc.core;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.junit.jupiter.api.Test;
@@ -30,50 +28,48 @@ import org.springframework.jdbc.core.test.ConstructorPersonWithSetters;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DataClassRowMapper}.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 5.3
*/
public class DataClassRowMapperTests extends AbstractRowMapperTests {
class DataClassRowMapperTests extends AbstractRowMapperTests {
@Test
public void testStaticQueryWithDataClass() throws Exception {
void staticQueryWithDataClass() throws Exception {
Mock mock = new Mock();
List<ConstructorPerson> result = mock.getJdbcTemplate().query(
ConstructorPerson person = mock.getJdbcTemplate().queryForObject(
"select name, age, birth_date, balance from people",
new DataClassRowMapper<>(ConstructorPerson.class));
assertThat(result.size()).isEqualTo(1);
verifyPerson(result.get(0));
verifyPerson(person);
mock.verifyClosed();
}
@Test
public void testStaticQueryWithDataClassAndGenerics() throws Exception {
void staticQueryWithDataClassAndGenerics() throws Exception {
Mock mock = new Mock();
List<ConstructorPersonWithGenerics> result = mock.getJdbcTemplate().query(
ConstructorPersonWithGenerics person = mock.getJdbcTemplate().queryForObject(
"select name, age, birth_date, balance from people",
new DataClassRowMapper<>(ConstructorPersonWithGenerics.class));
assertThat(result.size()).isEqualTo(1);
ConstructorPersonWithGenerics person = result.get(0);
assertThat(person.name()).isEqualTo("Bubba");
assertThat(person.age()).isEqualTo(22L);
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(person.balance()).isEqualTo(Collections.singletonList(new BigDecimal("1234.56")));
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new Date(1221222L));
assertThat(person.balance()).containsExactly(new BigDecimal("1234.56"));
mock.verifyClosed();
}
@Test
public void testStaticQueryWithDataClassAndSetters() throws Exception {
void staticQueryWithDataClassAndSetters() throws Exception {
Mock mock = new Mock(MockType.FOUR);
List<ConstructorPersonWithSetters> result = mock.getJdbcTemplate().query(
ConstructorPersonWithSetters person = mock.getJdbcTemplate().queryForObject(
"select name, age, birthdate, balance from people",
new DataClassRowMapper<>(ConstructorPersonWithSetters.class));
assertThat(result.size()).isEqualTo(1);
ConstructorPersonWithSetters person = result.get(0);
assertThat(person.name()).isEqualTo("BUBBA");
assertThat(person.age()).isEqualTo(22L);
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new Date(1221222L));
assertThat(person.balance()).isEqualTo(new BigDecimal("1234.56"));
mock.verifyClosed();
@@ -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.
@@ -31,6 +31,9 @@ public class Person {
private BigDecimal balance;
public Person() {
this.age = 42; // custom "default" value for a primitive
}
public String getName() {
return name;

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