mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2af9d961e4 | |||
| 8e64701cb7 | |||
| ae9a9e3c06 | |||
| c94025699f | |||
| ec3f59e6fe | |||
| 49ee4a4fdf | |||
| 698f8995f7 | |||
| ee51dab1f3 | |||
| 743a96b75e | |||
| b766a49a4d | |||
| 3ff308403a | |||
| 27f3feea1a | |||
| b42b785cd1 | |||
| f9d8367379 | |||
| 09b19d7aa6 | |||
| 8ade083a05 | |||
| db9e89fba9 | |||
| d7d44c6c51 | |||
| 330d2a5982 | |||
| 86fbb8678a | |||
| 55b258f2de | |||
| f4b3333fa8 | |||
| e5878ab15b | |||
| dedbbf0a79 | |||
| 5f02323b9c | |||
| 902cdd1a2f | |||
| deabd66939 | |||
| 2a853aea67 | |||
| a281d8c3fd | |||
| 638d91f5f4 | |||
| 6abfe041b1 | |||
| 462d33e68d | |||
| fded79d807 | |||
| c14cbd07f4 | |||
| 58bb6e7181 | |||
| e1df056059 | |||
| 46329851c1 | |||
| 51d109de30 | |||
| 3d89acf9ea | |||
| 9410998897 | |||
| 12cc8a9f07 | |||
| 4c38777064 | |||
| fb291379e4 | |||
| 182ba4ac29 | |||
| dbcfeb8f4f | |||
| 6f64cfd1e5 | |||
| 652781c4a1 | |||
| 8e25e32eb8 | |||
| 4b0bf16389 | |||
| 33023b240f | |||
| c407dc3df8 | |||
| 1439c5bb8f | |||
| 3f42d4de43 | |||
| 5ac687f4aa | |||
| 332b25b680 | |||
| 9876701493 | |||
| 828f74f71a | |||
| 965dd66f8c | |||
| b71d95df71 | |||
| b20de758b2 | |||
| 1c1a0afbed | |||
| a599601dd9 | |||
| 5eee4673c1 | |||
| 8caed88c14 | |||
| 3dc60f1627 | |||
| fee38175b3 | |||
| eb2c1cbcd4 | |||
| 8d0ac214f3 | |||
| 7241c30141 | |||
| 777caef4f7 | |||
| 9465110917 | |||
| b71e686cbd | |||
| 433a23a0bd | |||
| 7309fe9f2e | |||
| 874a296a76 | |||
| d872e7c326 | |||
| 4ca574cde1 | |||
| 98ecf0a412 | |||
| ddef70935a | |||
| 1d0dc43e5f | |||
| d8e4dc967f | |||
| f0f633d187 | |||
| 4b7aa6bb39 | |||
| 13933ee2c6 | |||
| ae8b7973b4 | |||
| a7f430bda8 | |||
| 5a781eeb54 | |||
| a879d18c9c | |||
| ce49068ff9 | |||
| 62a0ab3667 | |||
| 92a231cf91 | |||
| 50dff77d01 | |||
| 0f479293b1 | |||
| 5f5d383eeb | |||
| 529481c44c | |||
| 35d379f9d3 | |||
| c871758a51 | |||
| 0d2bfc926f | |||
| 4e97776969 | |||
| d42f950a36 | |||
| 4eabe29b9a | |||
| cd10171f98 | |||
| 597c687da8 | |||
| 081d70123e | |||
| d9c2ccb4b3 | |||
| c503f356c7 | |||
| 7f9933fdb7 | |||
| a425512025 | |||
| 298c9a6f1b | |||
| 8dcb2a75fa | |||
| 111a9902ac | |||
| 0389fcca7e | |||
| bb3ada4a67 | |||
| 6a68bd58f9 | |||
| b1414bf15b | |||
| f91943977c | |||
| 4c0ece944a | |||
| 2c75eb8745 | |||
| 711820ec70 | |||
| 69d87f16ee | |||
| 38f94799f4 | |||
| 4264d0becd | |||
| 7e7d6b9c3a | |||
| dfc06eda7a | |||
| f0aa6ebed6 | |||
| 6806aaf162 | |||
| ccec75c98d | |||
| 22029b4487 | |||
| d6b60046ce | |||
| 6685e78c36 | |||
| 8685b2f5bf | |||
| 4af225405c | |||
| 51e594ae81 | |||
| c2554b2b27 | |||
| 86a762b5ee | |||
| 211241befe | |||
| 0caa2ac696 | |||
| 4eb8a5c082 | |||
| d080b64907 | |||
| 5ab55dfb64 | |||
| 04209def46 | |||
| 3a61930bd0 | |||
| 456c7889a3 | |||
| 982d8ea84a | |||
| 67eb2bbf19 | |||
| a6596e2446 | |||
| 812b089f1d | |||
| 2172b99fee | |||
| b6144e5682 | |||
| 6e6f8637d1 | |||
| 00f7f5ff0a | |||
| 3d4a778b73 | |||
| 446c1add4f | |||
| aef4b0ec5a | |||
| 675e0b94c1 | |||
| b8b54ee524 | |||
| 97ea8a6789 | |||
| 95a400abfb | |||
| 4383a673bb | |||
| f7b94fc3e8 | |||
| 93760d1211 | |||
| 421b5cb311 | |||
| 1cc36e5be9 | |||
| f7c32d3a0b | |||
| 9c0a2eeeb2 | |||
| 7fa9f1c9cf | |||
| 1e03b30d33 | |||
| 7df149c8b8 |
@@ -1,10 +1,13 @@
|
||||
name: "Validate Gradle Wrapper"
|
||||
on: [push, pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
@@ -24,6 +24,7 @@ buildSrc/build
|
||||
/spring-core/graalvm/build
|
||||
/spring-core/kotlin-coroutines/build
|
||||
/framework-bom/build
|
||||
/framework-docs/build
|
||||
/integration-tests/build
|
||||
/src/asciidoc/build
|
||||
target/
|
||||
|
||||
@@ -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.333-librca
|
||||
java=8.0.352-librca
|
||||
|
||||
+31
-29
@@ -1,17 +1,17 @@
|
||||
plugins {
|
||||
id 'io.spring.dependency-management' version '1.0.11.RELEASE' apply false
|
||||
id 'io.spring.nohttp' version '0.0.10'
|
||||
id "io.freefair.aspectj" version '6.2.0' apply false
|
||||
id 'io.freefair.aspectj' version '6.2.0' apply false
|
||||
id 'org.jetbrains.dokka' version '1.6.10' apply false
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.5.32' apply false
|
||||
id "org.jetbrains.kotlin.plugin.serialization" version "1.5.32" apply false
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "1.5.32" apply false
|
||||
id 'org.asciidoctor.jvm.convert' version '3.3.2'
|
||||
id 'org.asciidoctor.jvm.pdf' version '3.3.2'
|
||||
id "org.unbroken-dome.xjc" version '2.0.0' apply false
|
||||
id "com.github.ben-manes.versions" version '0.39.0'
|
||||
id "com.github.johnrengelman.shadow" version '7.0.0' apply false
|
||||
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
|
||||
id 'com.github.ben-manes.versions' version '0.39.0'
|
||||
id 'com.github.johnrengelman.shadow' version '7.0.0' apply false
|
||||
id 'de.undercouch.download' version '4.1.2'
|
||||
id "me.champeau.jmh" version "0.6.6" apply false
|
||||
id 'me.champeau.jmh' version "0.6.6" apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -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.79.Final"
|
||||
mavenBom "io.projectreactor:reactor-bom:2020.0.21"
|
||||
mavenBom "io.netty:netty-bom:4.1.85.Final"
|
||||
mavenBom "io.projectreactor:reactor-bom:2020.0.25"
|
||||
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
|
||||
mavenBom "io.rsocket:rsocket-bom:1.1.2"
|
||||
mavenBom "org.eclipse.jetty:jetty-bom:9.4.48.v20220622"
|
||||
mavenBom "io.rsocket:rsocket-bom:1.1.3"
|
||||
mavenBom "org.eclipse.jetty:jetty-bom:9.4.49.v20220914"
|
||||
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.18.0') {
|
||||
dependencySet(group: 'org.apache.logging.log4j', version: '2.19.0') {
|
||||
entry 'log4j-api'
|
||||
entry 'log4j-core'
|
||||
entry 'log4j-jul'
|
||||
@@ -67,8 +67,8 @@ configure(allprojects) { project ->
|
||||
dependency "io.reactivex:rxjava:1.3.8"
|
||||
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
|
||||
dependency "io.reactivex.rxjava2:rxjava:2.2.21"
|
||||
dependency "io.reactivex.rxjava3:rxjava:3.1.4"
|
||||
dependency "io.smallrye.reactive:mutiny:1.4.0"
|
||||
dependency "io.reactivex.rxjava3:rxjava:3.1.5"
|
||||
dependency "io.smallrye.reactive:mutiny:1.8.0"
|
||||
dependency "io.projectreactor.tools:blockhound:1.0.6.RELEASE"
|
||||
|
||||
dependency "com.caucho:hessian:4.0.63"
|
||||
@@ -96,7 +96,7 @@ configure(allprojects) { project ->
|
||||
|
||||
dependency "com.h2database:h2:2.1.214"
|
||||
dependency "com.github.ben-manes.caffeine:caffeine:2.9.3"
|
||||
dependency "com.github.librepdf:openpdf:1.3.29"
|
||||
dependency "com.github.librepdf:openpdf:1.3.30"
|
||||
dependency "com.rometools:rome:1.18.0"
|
||||
dependency "commons-io:commons-io:2.5"
|
||||
dependency "io.vavr:vavr:0.10.4"
|
||||
@@ -124,22 +124,22 @@ configure(allprojects) { project ->
|
||||
dependency "org.ehcache:jcache:1.0.1"
|
||||
dependency "org.ehcache:ehcache:3.4.0"
|
||||
dependency "org.hibernate:hibernate-core:5.4.33.Final"
|
||||
dependency "org.hibernate:hibernate-validator:6.2.3.Final"
|
||||
dependency "org.hibernate:hibernate-validator:6.2.5.Final"
|
||||
dependency "org.webjars:webjars-locator-core:0.48"
|
||||
dependency "org.webjars:underscorejs:1.8.3"
|
||||
|
||||
dependencySet(group: 'org.apache.tomcat', version: '9.0.64') {
|
||||
dependencySet(group: 'org.apache.tomcat', version: '9.0.68') {
|
||||
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.64') {
|
||||
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.68') {
|
||||
entry 'tomcat-embed-core'
|
||||
entry 'tomcat-embed-websocket'
|
||||
}
|
||||
dependencySet(group: 'io.undertow', version: '2.2.18.Final') {
|
||||
dependencySet(group: 'io.undertow', version: '2.2.21.Final') {
|
||||
entry 'undertow-core'
|
||||
entry('undertow-servlet') {
|
||||
exclude group: "org.jboss.spec.javax.servlet", name: "jboss-servlet-api_4.0_spec"
|
||||
@@ -150,7 +150,7 @@ configure(allprojects) { project ->
|
||||
}
|
||||
}
|
||||
|
||||
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.12"
|
||||
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.13"
|
||||
dependency 'org.apache.httpcomponents.client5:httpclient5:5.1.3'
|
||||
dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.1.3'
|
||||
dependency("org.apache.httpcomponents:httpclient:4.5.13") {
|
||||
@@ -198,7 +198,7 @@ configure(allprojects) { project ->
|
||||
exclude group: "org.hamcrest", name: "hamcrest-core"
|
||||
}
|
||||
}
|
||||
dependencySet(group: 'org.mockito', version: '4.6.1') {
|
||||
dependencySet(group: 'org.mockito', version: '4.8.1') {
|
||||
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.63.0") {
|
||||
dependency("net.sourceforge.htmlunit:htmlunit:2.66.0") {
|
||||
exclude group: "commons-logging", name: "commons-logging"
|
||||
}
|
||||
dependency("org.seleniumhq.selenium:htmlunit-driver:2.63.0") {
|
||||
dependency("org.seleniumhq.selenium:htmlunit-driver:2.66.0") {
|
||||
exclude group: "commons-logging", name: "commons-logging"
|
||||
}
|
||||
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
|
||||
@@ -217,7 +217,7 @@ configure(allprojects) { project ->
|
||||
exclude group: "io.netty", name: "netty"
|
||||
}
|
||||
dependency "org.skyscreamer:jsonassert:1.5.0"
|
||||
dependency "com.jayway.jsonpath:json-path:2.6.0"
|
||||
dependency "com.jayway.jsonpath:json-path:2.7.0"
|
||||
dependency "org.bouncycastle:bcpkix-jdk18on:1.71"
|
||||
|
||||
dependencySet(group: 'org.apache.tiles', version: '3.0.8') {
|
||||
@@ -340,7 +340,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "10.3.1"
|
||||
toolVersion = "10.4"
|
||||
configDirectory.set(rootProject.file("src/checkstyle"))
|
||||
}
|
||||
|
||||
@@ -380,8 +380,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
"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/",
|
||||
// TODO Uncomment once httpclient5 API docs are again online.
|
||||
// "https://hc.apache.org/httpcomponents-client-5.1.x/current/httpclient5/apidocs/",
|
||||
"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/",
|
||||
// Disabling linking to JUnit 5.8.2, since the `package-list` file no longer exists due to
|
||||
@@ -414,11 +413,9 @@ configure(rootProject) {
|
||||
|
||||
nohttp {
|
||||
source.exclude "**/test-output/**"
|
||||
source.exclude "spring-core/graalvm/build/**" // only available on `main`
|
||||
source.exclude "spring-core-test/build/**" // only available on `main`
|
||||
allowlistFile = project.file("src/nohttp/allowlist.lines")
|
||||
def rootPath = file(rootDir).toPath()
|
||||
def projectDirs = allprojects.collect { it.projectDir } + "${rootDir}/buildSrc"
|
||||
def projectDirs = allprojects.collect { it.projectDir } + "${rootDir}/spring-core-test" + "${rootDir}/buildSrc" + "${rootDir}/framework-docs"
|
||||
projectDirs.forEach { dir ->
|
||||
[ 'bin', 'build', 'out', '.settings' ]
|
||||
.collect { rootPath.relativize(new File(dir, it).toPath()) }
|
||||
@@ -429,6 +426,10 @@ configure(rootProject) {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("checkstyleNohttp").configure {
|
||||
maxHeapSize = "1g"
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
@@ -438,4 +439,5 @@ configure(rootProject) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,3 +15,6 @@ changelog:
|
||||
sort: "title"
|
||||
labels:
|
||||
- "type: dependency-upgrade"
|
||||
contributors:
|
||||
exclude:
|
||||
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:focal-20220531
|
||||
FROM ubuntu:focal-20220922
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
|
||||
@@ -3,16 +3,16 @@ set -e
|
||||
|
||||
case "$1" in
|
||||
java8)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/8u333+2/bellsoft-jdk8u333+2-linux-amd64.tar.gz"
|
||||
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.15.1+2/bellsoft-jdk11.0.15.1+2-linux-amd64.tar.gz"
|
||||
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.3.1+2/bellsoft-jdk17.0.3.1+2-linux-amd64.tar.gz"
|
||||
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.1.1+2/bellsoft-jdk18.0.1.1+2-linux-amd64.tar.gz"
|
||||
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"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=5.3.22
|
||||
version=5.3.24
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
+1
-1
@@ -153,7 +153,7 @@ asciidoctorPdf {
|
||||
task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor', 'asciidoctorPdf', 'dokkaHtmlMultiModule']) {
|
||||
group = "Distribution"
|
||||
description = "Builds -${archiveClassifier} archive containing api and reference " +
|
||||
"for deployment at https://docs.spring.io/spring-framework/docs."
|
||||
"for deployment at https://docs.spring.io/spring-framework/docs/."
|
||||
|
||||
archiveBaseName.set("spring-framework")
|
||||
archiveClassifier.set("docs")
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
+1
-1
@@ -31,6 +31,6 @@ You'll notice these files are already intentionally in .gitignore. The same poli
|
||||
|
||||
## FAQ
|
||||
|
||||
Q. What about IntelliJ IDEA's own [Gradle support](https://confluence.jetbrains.net/display/IDEADEV/Gradle+integration)?
|
||||
Q. What about IntelliJ IDEA's own [Gradle support](https://www.jetbrains.com/help/idea/gradle.html)?
|
||||
|
||||
A. Keep an eye on https://youtrack.jetbrains.com/issue/IDEA-53476
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ pluginManagement {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "com.gradle.enterprise" version "3.10.2"
|
||||
id "io.spring.ge.conventions" version "0.0.10"
|
||||
id "com.gradle.enterprise" version "3.11.1"
|
||||
id "io.spring.ge.conventions" version "0.0.11"
|
||||
}
|
||||
|
||||
include "spring-aop"
|
||||
|
||||
+1
-1
@@ -147,7 +147,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
|
||||
/**
|
||||
* Set whether the proxy should be frozen, preventing advice
|
||||
* from being added to it once it is created.
|
||||
* <p>Overridden from the super class to prevent the proxy configuration
|
||||
* <p>Overridden from the superclass to prevent the proxy configuration
|
||||
* from being frozen before the proxy is created.
|
||||
*/
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ public abstract aspect AbstractDependencyInjectionAspect {
|
||||
mostSpecificSubTypeConstruction() && !preConstructionConfiguration();
|
||||
|
||||
/**
|
||||
* Select least specific super type that is marked for DI
|
||||
* Select least specific supertype that is marked for DI
|
||||
* (so that injection occurs only once with pre-construction injection).
|
||||
*/
|
||||
public abstract pointcut leastSpecificSuperTypeConstruction();
|
||||
|
||||
+10
-10
@@ -97,8 +97,6 @@ public final class CachedIntrospectionResults {
|
||||
*/
|
||||
public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
|
||||
|
||||
private static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};
|
||||
|
||||
|
||||
private static final boolean shouldIntrospectorIgnoreBeaninfoClasses =
|
||||
SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);
|
||||
@@ -297,7 +295,7 @@ public final class CachedIntrospectionResults {
|
||||
// Only allow URL attribute introspection, not content resolution
|
||||
continue;
|
||||
}
|
||||
if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType())) {
|
||||
if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType(), beanClass)) {
|
||||
// Ignore read-only properties such as ClassLoader - no need to bind to those
|
||||
continue;
|
||||
}
|
||||
@@ -347,7 +345,8 @@ public final class CachedIntrospectionResults {
|
||||
// GenericTypeAwarePropertyDescriptor leniently resolves a set* write method
|
||||
// against a declared read method, so we prefer read method descriptors here.
|
||||
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
|
||||
if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType())) {
|
||||
if (pd.getWriteMethod() == null &&
|
||||
isInvalidReadOnlyPropertyType(pd.getPropertyType(), beanClass)) {
|
||||
// Ignore read-only properties such as ClassLoader - no need to bind to those
|
||||
continue;
|
||||
}
|
||||
@@ -380,7 +379,7 @@ public final class CachedIntrospectionResults {
|
||||
if (Modifier.isStatic(method.getModifiers()) ||
|
||||
method.getDeclaringClass() == Object.class || method.getDeclaringClass() == Class.class ||
|
||||
method.getParameterCount() > 0 || method.getReturnType() == void.class ||
|
||||
isInvalidReadOnlyPropertyType(method.getReturnType())) {
|
||||
isInvalidReadOnlyPropertyType(method.getReturnType(), method.getDeclaringClass())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
@@ -393,10 +392,11 @@ public final class CachedIntrospectionResults {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInvalidReadOnlyPropertyType(@Nullable Class<?> returnType) {
|
||||
return (returnType != null && (AutoCloseable.class.isAssignableFrom(returnType) ||
|
||||
ClassLoader.class.isAssignableFrom(returnType) ||
|
||||
ProtectionDomain.class.isAssignableFrom(returnType)));
|
||||
private boolean isInvalidReadOnlyPropertyType(@Nullable Class<?> returnType, Class<?> beanClass) {
|
||||
return (returnType != null && (ClassLoader.class.isAssignableFrom(returnType) ||
|
||||
ProtectionDomain.class.isAssignableFrom(returnType) ||
|
||||
(AutoCloseable.class.isAssignableFrom(returnType) &&
|
||||
!AutoCloseable.class.isAssignableFrom(beanClass))));
|
||||
}
|
||||
|
||||
|
||||
@@ -422,7 +422,7 @@ public final class CachedIntrospectionResults {
|
||||
}
|
||||
|
||||
PropertyDescriptor[] getPropertyDescriptors() {
|
||||
return this.propertyDescriptors.values().toArray(EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
|
||||
return this.propertyDescriptors.values().toArray(PropertyDescriptorUtils.EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
|
||||
}
|
||||
|
||||
private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class<?> beanClass, PropertyDescriptor pd) {
|
||||
|
||||
@@ -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.
|
||||
@@ -17,9 +17,15 @@
|
||||
package org.springframework.beans;
|
||||
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -32,6 +38,80 @@ import org.springframework.util.ObjectUtils;
|
||||
*/
|
||||
abstract class PropertyDescriptorUtils {
|
||||
|
||||
public static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};
|
||||
|
||||
|
||||
/**
|
||||
* Simple introspection algorithm for basic set/get/is accessor methods,
|
||||
* building corresponding JavaBeans property descriptors for them.
|
||||
* <p>This just supports the basic JavaBeans conventions, without indexed
|
||||
* properties or any customizers, and without other BeanInfo metadata.
|
||||
* For standard JavaBeans introspection, use the JavaBeans Introspector.
|
||||
* @param beanClass the target class to introspect
|
||||
* @return a collection of property descriptors
|
||||
* @throws IntrospectionException from introspecting the given bean class
|
||||
* @since 5.3.24
|
||||
* @see SimpleBeanInfoFactory
|
||||
* @see java.beans.Introspector#getBeanInfo(Class)
|
||||
*/
|
||||
public static Collection<? extends PropertyDescriptor> determineBasicProperties(Class<?> beanClass)
|
||||
throws IntrospectionException {
|
||||
|
||||
Map<String, BasicPropertyDescriptor> pdMap = new TreeMap<>();
|
||||
|
||||
for (Method method : beanClass.getMethods()) {
|
||||
String methodName = method.getName();
|
||||
|
||||
boolean setter;
|
||||
int nameIndex;
|
||||
if (methodName.startsWith("set") && method.getParameterCount() == 1) {
|
||||
setter = true;
|
||||
nameIndex = 3;
|
||||
}
|
||||
else if (methodName.startsWith("get") && method.getParameterCount() == 0 && method.getReturnType() != Void.TYPE) {
|
||||
setter = false;
|
||||
nameIndex = 3;
|
||||
}
|
||||
else if (methodName.startsWith("is") && method.getParameterCount() == 0 && method.getReturnType() == boolean.class) {
|
||||
setter = false;
|
||||
nameIndex = 2;
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
|
||||
String propertyName = Introspector.decapitalize(methodName.substring(nameIndex));
|
||||
if (propertyName.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BasicPropertyDescriptor pd = pdMap.get(propertyName);
|
||||
if (pd != null) {
|
||||
if (setter) {
|
||||
if (pd.getWriteMethod() == null ||
|
||||
pd.getWriteMethod().getParameterTypes()[0].isAssignableFrom(method.getParameterTypes()[0])) {
|
||||
pd.setWriteMethod(method);
|
||||
}
|
||||
else {
|
||||
pd.addWriteMethod(method);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (pd.getReadMethod() == null ||
|
||||
(pd.getReadMethod().getReturnType() == method.getReturnType() && method.getName().startsWith("is"))) {
|
||||
pd.setReadMethod(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
pd = new BasicPropertyDescriptor(propertyName, (!setter ? method : null), (setter ? method : null));
|
||||
pdMap.put(propertyName, pd);
|
||||
}
|
||||
}
|
||||
|
||||
return pdMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link java.beans.FeatureDescriptor}.
|
||||
*/
|
||||
@@ -173,4 +253,72 @@ abstract class PropertyDescriptorUtils {
|
||||
pd.isBound() == otherPd.isBound() && pd.isConstrained() == otherPd.isConstrained());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* PropertyDescriptor for {@link #determineBasicProperties(Class)},
|
||||
* not performing any early type determination for
|
||||
* {@link #setReadMethod}/{@link #setWriteMethod}.
|
||||
* @since 5.3.24
|
||||
*/
|
||||
private static class BasicPropertyDescriptor extends PropertyDescriptor {
|
||||
|
||||
@Nullable
|
||||
private Method readMethod;
|
||||
|
||||
@Nullable
|
||||
private Method writeMethod;
|
||||
|
||||
private final List<Method> alternativeWriteMethods = new ArrayList<>();
|
||||
|
||||
public BasicPropertyDescriptor(String propertyName, @Nullable Method readMethod, @Nullable Method writeMethod)
|
||||
throws IntrospectionException {
|
||||
|
||||
super(propertyName, readMethod, writeMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadMethod(@Nullable Method readMethod) {
|
||||
this.readMethod = readMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Method getReadMethod() {
|
||||
return this.readMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWriteMethod(@Nullable Method writeMethod) {
|
||||
this.writeMethod = writeMethod;
|
||||
}
|
||||
|
||||
public void addWriteMethod(Method writeMethod) {
|
||||
if (this.writeMethod != null) {
|
||||
this.alternativeWriteMethods.add(this.writeMethod);
|
||||
this.writeMethod = null;
|
||||
}
|
||||
this.alternativeWriteMethods.add(writeMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Method getWriteMethod() {
|
||||
if (this.writeMethod == null && !this.alternativeWriteMethods.isEmpty()) {
|
||||
if (this.readMethod == null) {
|
||||
return this.alternativeWriteMethods.get(0);
|
||||
}
|
||||
else {
|
||||
for (Method method : this.alternativeWriteMethods) {
|
||||
if (this.readMethod.getReturnType().isAssignableFrom(method.getParameterTypes()[0])) {
|
||||
this.writeMethod = method;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.writeMethod;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.beans;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.beans.SimpleBeanInfo;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* {@link BeanInfoFactory} implementation that bypasses the standard {@link java.beans.Introspector}
|
||||
* for faster introspection, reduced to basic property determination (as commonly needed in Spring).
|
||||
*
|
||||
* <p>To be configured via a {@code META-INF/spring.factories} file with the following content,
|
||||
* overriding other custom {@code org.springframework.beans.BeanInfoFactory} declarations:
|
||||
* {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.SimpleBeanInfoFactory}
|
||||
*
|
||||
* <p>Ordered at {@code Ordered.LOWEST_PRECEDENCE - 1} to override {@link ExtendedBeanInfoFactory}
|
||||
* (registered by default in 5.3) if necessary while still allowing other user-defined
|
||||
* {@link BeanInfoFactory} types to take precedence.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.3.24
|
||||
* @see ExtendedBeanInfoFactory
|
||||
* @see CachedIntrospectionResults
|
||||
*/
|
||||
public class SimpleBeanInfoFactory implements BeanInfoFactory, Ordered {
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
|
||||
Collection<? extends PropertyDescriptor> pds =
|
||||
PropertyDescriptorUtils.determineBasicProperties(beanClass);
|
||||
|
||||
return new SimpleBeanInfo() {
|
||||
@Override
|
||||
public PropertyDescriptor[] getPropertyDescriptors() {
|
||||
return pds.toArray(PropertyDescriptorUtils.EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE - 1;
|
||||
}
|
||||
|
||||
}
|
||||
+3
-2
@@ -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.
|
||||
@@ -438,7 +438,6 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
|
||||
// Fall back to class name as cache key, for backwards compatibility with custom callers.
|
||||
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
|
||||
@@ -563,7 +562,9 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
* @param type the type of the bean
|
||||
* @return the target beans, or an empty Collection if no bean of this type is found
|
||||
* @throws BeansException if bean retrieval failed
|
||||
* @deprecated since 5.3.24 since it is unused in the meantime
|
||||
*/
|
||||
@Deprecated
|
||||
protected <T> Map<String, T> findAutowireCandidates(Class<T> type) throws BeansException {
|
||||
if (this.beanFactory == null) {
|
||||
throw new IllegalStateException("No BeanFactory configured - " +
|
||||
|
||||
+48
-34
@@ -161,7 +161,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
|
||||
/** Cache of pre-filtered post-processors. */
|
||||
@Nullable
|
||||
private volatile BeanPostProcessorCache beanPostProcessorCache;
|
||||
private BeanPostProcessorCache beanPostProcessorCache;
|
||||
|
||||
/** Map from scope identifier String to corresponding Scope. */
|
||||
private final Map<String, Scope> scopes = new LinkedHashMap<>(8);
|
||||
@@ -944,10 +944,12 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
@Override
|
||||
public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) {
|
||||
Assert.notNull(beanPostProcessor, "BeanPostProcessor must not be null");
|
||||
// Remove from old position, if any
|
||||
this.beanPostProcessors.remove(beanPostProcessor);
|
||||
// Add to end of list
|
||||
this.beanPostProcessors.add(beanPostProcessor);
|
||||
synchronized (this.beanPostProcessors) {
|
||||
// Remove from old position, if any
|
||||
this.beanPostProcessors.remove(beanPostProcessor);
|
||||
// Add to end of list
|
||||
this.beanPostProcessors.add(beanPostProcessor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -957,8 +959,12 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
* @see #addBeanPostProcessor
|
||||
*/
|
||||
public void addBeanPostProcessors(Collection<? extends BeanPostProcessor> beanPostProcessors) {
|
||||
this.beanPostProcessors.removeAll(beanPostProcessors);
|
||||
this.beanPostProcessors.addAll(beanPostProcessors);
|
||||
synchronized (this.beanPostProcessors) {
|
||||
// Remove from old position, if any
|
||||
this.beanPostProcessors.removeAll(beanPostProcessors);
|
||||
// Add to end of list
|
||||
this.beanPostProcessors.addAll(beanPostProcessors);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -980,26 +986,34 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
* @since 5.3
|
||||
*/
|
||||
BeanPostProcessorCache getBeanPostProcessorCache() {
|
||||
BeanPostProcessorCache bpCache = this.beanPostProcessorCache;
|
||||
if (bpCache == null) {
|
||||
bpCache = new BeanPostProcessorCache();
|
||||
for (BeanPostProcessor bp : this.beanPostProcessors) {
|
||||
if (bp instanceof InstantiationAwareBeanPostProcessor) {
|
||||
bpCache.instantiationAware.add((InstantiationAwareBeanPostProcessor) bp);
|
||||
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
|
||||
bpCache.smartInstantiationAware.add((SmartInstantiationAwareBeanPostProcessor) bp);
|
||||
synchronized (this.beanPostProcessors) {
|
||||
BeanPostProcessorCache bppCache = this.beanPostProcessorCache;
|
||||
if (bppCache == null) {
|
||||
bppCache = new BeanPostProcessorCache();
|
||||
for (BeanPostProcessor bpp : this.beanPostProcessors) {
|
||||
if (bpp instanceof InstantiationAwareBeanPostProcessor) {
|
||||
bppCache.instantiationAware.add((InstantiationAwareBeanPostProcessor) bpp);
|
||||
if (bpp instanceof SmartInstantiationAwareBeanPostProcessor) {
|
||||
bppCache.smartInstantiationAware.add((SmartInstantiationAwareBeanPostProcessor) bpp);
|
||||
}
|
||||
}
|
||||
if (bpp instanceof DestructionAwareBeanPostProcessor) {
|
||||
bppCache.destructionAware.add((DestructionAwareBeanPostProcessor) bpp);
|
||||
}
|
||||
if (bpp instanceof MergedBeanDefinitionPostProcessor) {
|
||||
bppCache.mergedDefinition.add((MergedBeanDefinitionPostProcessor) bpp);
|
||||
}
|
||||
}
|
||||
if (bp instanceof DestructionAwareBeanPostProcessor) {
|
||||
bpCache.destructionAware.add((DestructionAwareBeanPostProcessor) bp);
|
||||
}
|
||||
if (bp instanceof MergedBeanDefinitionPostProcessor) {
|
||||
bpCache.mergedDefinition.add((MergedBeanDefinitionPostProcessor) bp);
|
||||
}
|
||||
this.beanPostProcessorCache = bppCache;
|
||||
}
|
||||
this.beanPostProcessorCache = bpCache;
|
||||
return bppCache;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetBeanPostProcessorCache() {
|
||||
synchronized (this.beanPostProcessors) {
|
||||
this.beanPostProcessorCache = null;
|
||||
}
|
||||
return bpCache;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2014,27 +2028,27 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
@Override
|
||||
public BeanPostProcessor set(int index, BeanPostProcessor element) {
|
||||
BeanPostProcessor result = super.set(index, element);
|
||||
beanPostProcessorCache = null;
|
||||
resetBeanPostProcessorCache();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(BeanPostProcessor o) {
|
||||
boolean success = super.add(o);
|
||||
beanPostProcessorCache = null;
|
||||
resetBeanPostProcessorCache();
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, BeanPostProcessor element) {
|
||||
super.add(index, element);
|
||||
beanPostProcessorCache = null;
|
||||
resetBeanPostProcessorCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanPostProcessor remove(int index) {
|
||||
BeanPostProcessor result = super.remove(index);
|
||||
beanPostProcessorCache = null;
|
||||
resetBeanPostProcessorCache();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2042,7 +2056,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
public boolean remove(Object o) {
|
||||
boolean success = super.remove(o);
|
||||
if (success) {
|
||||
beanPostProcessorCache = null;
|
||||
resetBeanPostProcessorCache();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@@ -2051,7 +2065,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
boolean success = super.removeAll(c);
|
||||
if (success) {
|
||||
beanPostProcessorCache = null;
|
||||
resetBeanPostProcessorCache();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@@ -2060,7 +2074,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
boolean success = super.retainAll(c);
|
||||
if (success) {
|
||||
beanPostProcessorCache = null;
|
||||
resetBeanPostProcessorCache();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@@ -2069,7 +2083,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
public boolean addAll(Collection<? extends BeanPostProcessor> c) {
|
||||
boolean success = super.addAll(c);
|
||||
if (success) {
|
||||
beanPostProcessorCache = null;
|
||||
resetBeanPostProcessorCache();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@@ -2078,7 +2092,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
public boolean addAll(int index, Collection<? extends BeanPostProcessor> c) {
|
||||
boolean success = super.addAll(index, c);
|
||||
if (success) {
|
||||
beanPostProcessorCache = null;
|
||||
resetBeanPostProcessorCache();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@@ -2087,7 +2101,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
public boolean removeIf(Predicate<? super BeanPostProcessor> filter) {
|
||||
boolean success = super.removeIf(filter);
|
||||
if (success) {
|
||||
beanPostProcessorCache = null;
|
||||
resetBeanPostProcessorCache();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@@ -2095,7 +2109,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
@Override
|
||||
public void replaceAll(UnaryOperator<BeanPostProcessor> operator) {
|
||||
super.replaceAll(operator);
|
||||
beanPostProcessorCache = null;
|
||||
resetBeanPostProcessorCache();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-10
@@ -2167,20 +2167,25 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
@Nullable
|
||||
public Object getOrderSource(Object obj) {
|
||||
String beanName = this.instancesToBeanNames.get(obj);
|
||||
if (beanName == null || !containsBeanDefinition(beanName)) {
|
||||
if (beanName == null) {
|
||||
return null;
|
||||
}
|
||||
RootBeanDefinition beanDefinition = getMergedLocalBeanDefinition(beanName);
|
||||
List<Object> sources = new ArrayList<>(2);
|
||||
Method factoryMethod = beanDefinition.getResolvedFactoryMethod();
|
||||
if (factoryMethod != null) {
|
||||
sources.add(factoryMethod);
|
||||
try {
|
||||
RootBeanDefinition beanDefinition = (RootBeanDefinition) getMergedBeanDefinition(beanName);
|
||||
List<Object> sources = new ArrayList<>(2);
|
||||
Method factoryMethod = beanDefinition.getResolvedFactoryMethod();
|
||||
if (factoryMethod != null) {
|
||||
sources.add(factoryMethod);
|
||||
}
|
||||
Class<?> targetType = beanDefinition.getTargetType();
|
||||
if (targetType != null && targetType != obj.getClass()) {
|
||||
sources.add(targetType);
|
||||
}
|
||||
return sources.toArray();
|
||||
}
|
||||
Class<?> targetType = beanDefinition.getTargetType();
|
||||
if (targetType != null && targetType != obj.getClass()) {
|
||||
sources.add(targetType);
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
return null;
|
||||
}
|
||||
return sources.toArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,6 +153,16 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests {
|
||||
assertThat(accessor.getPropertyValue("object")).isEqualTo(8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setterOverload() {
|
||||
SetterOverload target = new SetterOverload();
|
||||
BeanWrapper accessor = createAccessor(target);
|
||||
accessor.setPropertyValue("object", "a String");
|
||||
assertThat(target.value).isEqualTo("a String");
|
||||
assertThat(target.getObject()).isEqualTo("a String");
|
||||
assertThat(accessor.getPropertyValue("object")).isEqualTo("a String");
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyDescriptors() throws Exception {
|
||||
TestBean target = new TestBean();
|
||||
@@ -206,6 +216,10 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests {
|
||||
assertThat(accessor.isReadableProperty("inputStream")).isFalse();
|
||||
assertThat(accessor.isReadableProperty("filename")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("description")).isTrue();
|
||||
|
||||
accessor = createAccessor(new ActiveResource());
|
||||
|
||||
assertThat(accessor.isReadableProperty("resource")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -348,6 +362,36 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests {
|
||||
}
|
||||
|
||||
|
||||
public static class SetterOverload {
|
||||
|
||||
public String value;
|
||||
|
||||
public void setObject(Integer length) {
|
||||
this.value = length.toString();
|
||||
}
|
||||
|
||||
public void setObject(String object) {
|
||||
this.value = object;
|
||||
}
|
||||
|
||||
public String getObject() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class ActiveResource implements AutoCloseable {
|
||||
|
||||
public ActiveResource getResource() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class GetterWithOptional {
|
||||
|
||||
public TestBean value;
|
||||
|
||||
+15
@@ -1982,6 +1982,21 @@ class DefaultListableBeanFactoryTests {
|
||||
assertThat(bean.getSpouse()).isEqualTo(lbf.getBean("spouse"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void beanProviderWithParentBeanFactoryReuseOrder() {
|
||||
DefaultListableBeanFactory parentBf = new DefaultListableBeanFactory();
|
||||
parentBf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
|
||||
parentBf.registerBeanDefinition("regular", new RootBeanDefinition(TestBean.class));
|
||||
parentBf.registerBeanDefinition("test", new RootBeanDefinition(HighPriorityTestBean.class));
|
||||
lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
|
||||
lbf.setParentBeanFactory(parentBf);
|
||||
lbf.registerBeanDefinition("low", new RootBeanDefinition(LowPriorityTestBean.class));
|
||||
List<Class<?>> orderedTypes = lbf.getBeanProvider(TestBean.class).orderedStream()
|
||||
.map(Object::getClass).collect(Collectors.toList());
|
||||
assertThat(orderedTypes).containsExactly(
|
||||
HighPriorityTestBean.class, LowPriorityTestBean.class, TestBean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void autowireExistingBeanByName() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(TestBean.class);
|
||||
|
||||
+2
-2
@@ -722,7 +722,7 @@ class CustomEditorTests {
|
||||
|
||||
@Test
|
||||
void testCustomDateEditor() {
|
||||
CustomDateEditor editor = new CustomDateEditor(null, false);
|
||||
CustomDateEditor editor = new CustomDateEditor(new SimpleDateFormat("MM/dd/yyyy"), false);
|
||||
editor.setValue(null);
|
||||
assertThat(editor.getValue()).isNull();
|
||||
assertThat(editor.getAsText()).isEmpty();
|
||||
@@ -730,7 +730,7 @@ class CustomEditorTests {
|
||||
|
||||
@Test
|
||||
void testCustomDateEditorWithEmptyAsNull() {
|
||||
CustomDateEditor editor = new CustomDateEditor(null, true);
|
||||
CustomDateEditor editor = new CustomDateEditor(new SimpleDateFormat("MM/dd/yyyy"), true);
|
||||
editor.setValue(null);
|
||||
assertThat(editor.getValue()).isNull();
|
||||
assertThat(editor.getAsText()).isEmpty();
|
||||
|
||||
+2
-2
@@ -81,7 +81,7 @@ class TypeHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the super class of the specified {@link Element} or null if this
|
||||
* Return the superclass of the specified {@link Element} or null if this
|
||||
* {@code element} represents {@link Object}.
|
||||
*/
|
||||
public Element getSuperClass(Element element) {
|
||||
@@ -100,7 +100,7 @@ class TypeHelper {
|
||||
public List<Element> getDirectInterfaces(Element element) {
|
||||
List<? extends TypeMirror> superTypes = this.types.directSupertypes(element.asType());
|
||||
List<Element> directInterfaces = new ArrayList<>();
|
||||
if (superTypes.size() > 1) { // index 0 is the super class
|
||||
if (superTypes.size() > 1) { // index 0 is the superclass
|
||||
for (int i = 1; i < superTypes.size(); i++) {
|
||||
Element e = this.types.asElement(superTypes.get(i));
|
||||
if (e != null) {
|
||||
|
||||
+10
-10
@@ -93,7 +93,7 @@ public class SimpleMailMessage implements MailMessage, Serializable {
|
||||
|
||||
|
||||
@Override
|
||||
public void setFrom(String from) {
|
||||
public void setFrom(@Nullable String from) {
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public class SimpleMailMessage implements MailMessage, Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReplyTo(String replyTo) {
|
||||
public void setReplyTo(@Nullable String replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public class SimpleMailMessage implements MailMessage, Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTo(String to) {
|
||||
public void setTo(@Nullable String to) {
|
||||
this.to = new String[] {to};
|
||||
}
|
||||
|
||||
@@ -128,12 +128,12 @@ public class SimpleMailMessage implements MailMessage, Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCc(String cc) {
|
||||
public void setCc(@Nullable String cc) {
|
||||
this.cc = new String[] {cc};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCc(String... cc) {
|
||||
public void setCc(@Nullable String... cc) {
|
||||
this.cc = cc;
|
||||
}
|
||||
|
||||
@@ -143,12 +143,12 @@ public class SimpleMailMessage implements MailMessage, Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBcc(String bcc) {
|
||||
public void setBcc(@Nullable String bcc) {
|
||||
this.bcc = new String[] {bcc};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBcc(String... bcc) {
|
||||
public void setBcc(@Nullable String... bcc) {
|
||||
this.bcc = bcc;
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ public class SimpleMailMessage implements MailMessage, Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSentDate(Date sentDate) {
|
||||
public void setSentDate(@Nullable Date sentDate) {
|
||||
this.sentDate = sentDate;
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ public class SimpleMailMessage implements MailMessage, Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubject(String subject) {
|
||||
public void setSubject(@Nullable String subject) {
|
||||
this.subject = subject;
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ public class SimpleMailMessage implements MailMessage, Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(String text) {
|
||||
public void setText(@Nullable String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ video/x-msvideo avi
|
||||
################################################################################
|
||||
#
|
||||
# Additional file types adapted from
|
||||
# http://sites.utoronto.ca/webdocs/HTMLdocs/Book/Book-3ed/appb/mimetype.html
|
||||
# https://web.archive.org/web/20220119153325/http%3A//sites.utoronto.ca/webdocs/HTMLdocs/Book/Book-3ed/appb/mimetype.html
|
||||
# kindly re-licensed to Apache Software License 2.0 by Ian Graham.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
@@ -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.
|
||||
@@ -24,7 +24,7 @@ import org.springframework.lang.Nullable;
|
||||
* Interface that defines common cache operations.
|
||||
*
|
||||
* <b>Note:</b> Due to the generic use of caching, it is recommended that
|
||||
* implementations allow storage of <tt>null</tt> values (for example to
|
||||
* implementations allow storage of {@code null} values (for example to
|
||||
* cache methods that return {@code null}).
|
||||
*
|
||||
* @author Costin Leau
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.
|
||||
@@ -111,7 +111,8 @@ public @interface CacheEvict {
|
||||
|
||||
/**
|
||||
* Spring Expression Language (SpEL) expression used for making the cache
|
||||
* eviction operation conditional.
|
||||
* eviction operation conditional. Evict that cache if the condition evaluates
|
||||
* to {@code true}.
|
||||
* <p>Default is {@code ""}, meaning the cache eviction is always performed.
|
||||
* <p>The SpEL expression evaluates against a dedicated context that provides the
|
||||
* following meta-data:
|
||||
|
||||
+4
-2
@@ -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.
|
||||
@@ -118,7 +118,8 @@ public @interface CachePut {
|
||||
|
||||
/**
|
||||
* Spring Expression Language (SpEL) expression used for making the cache
|
||||
* put operation conditional.
|
||||
* put operation conditional. Update the cache if the condition evaluates to
|
||||
* {@code true}.
|
||||
* <p>This expression is evaluated after the method has been called due to the
|
||||
* nature of the put operation and can therefore refer to the {@code result}.
|
||||
* <p>Default is {@code ""}, meaning the method result is always cached.
|
||||
@@ -142,6 +143,7 @@ public @interface CachePut {
|
||||
|
||||
/**
|
||||
* Spring Expression Language (SpEL) expression used to veto the cache put operation.
|
||||
* Veto updating the cache if the condition evaluates to {@code true}.
|
||||
* <p>Default is {@code ""}, meaning that caching is never vetoed.
|
||||
* <p>The SpEL expression evaluates against a dedicated context that provides the
|
||||
* following meta-data:
|
||||
|
||||
+4
-2
@@ -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.
|
||||
@@ -123,7 +123,8 @@ public @interface Cacheable {
|
||||
|
||||
/**
|
||||
* Spring Expression Language (SpEL) expression used for making the method
|
||||
* caching conditional.
|
||||
* caching conditional. Cache the result if the condition evaluates to
|
||||
* {@code true}.
|
||||
* <p>Default is {@code ""}, meaning the method result is always cached.
|
||||
* <p>The SpEL expression evaluates against a dedicated context that provides the
|
||||
* following meta-data:
|
||||
@@ -142,6 +143,7 @@ public @interface Cacheable {
|
||||
|
||||
/**
|
||||
* Spring Expression Language (SpEL) expression used to veto method caching.
|
||||
* Veto caching the result if the condition evaluates to {@code true}.
|
||||
* <p>Unlike {@link #condition}, this expression is evaluated after the method
|
||||
* has been called and can therefore refer to the {@code result}.
|
||||
* <p>Default is {@code ""}, meaning that caching is never vetoed.
|
||||
|
||||
Vendored
+17
@@ -77,6 +77,23 @@ public class LoggingCacheErrorHandler implements CacheErrorHandler {
|
||||
this.logStackTraces = logStackTraces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code LoggingCacheErrorHandler} that uses the supplied
|
||||
* {@code loggerName} and {@code logStackTraces} flag.
|
||||
* @param loggerName the name of the logger to use. The name will be passed
|
||||
* to the underlying logger implementation through Commons Logging, getting
|
||||
* interpreted as log category according to the logger's configuration.
|
||||
* @param logStackTraces whether to log stack traces
|
||||
* @since 5.3.24
|
||||
* @see org.apache.commons.logging.LogFactory#getLog(String)
|
||||
* @see java.util.logging.Logger#getLogger(String)
|
||||
*/
|
||||
public LoggingCacheErrorHandler(String loggerName, boolean logStackTraces) {
|
||||
Assert.notNull(loggerName, "'loggerName' must not be null");
|
||||
this.logger = LogFactory.getLog(loggerName);
|
||||
this.logStackTraces = logStackTraces;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
|
||||
|
||||
+5
-4
@@ -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.
|
||||
@@ -54,7 +54,6 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
|
||||
import org.springframework.context.annotation.DeferredImportSelector.Group;
|
||||
import org.springframework.core.NestedIOException;
|
||||
import org.springframework.core.OrderComparator;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
@@ -680,6 +679,7 @@ class ConfigurationClassParser {
|
||||
/**
|
||||
* Factory method to obtain a {@link SourceClass} from a class name.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
SourceClass asSourceClass(@Nullable String className, Predicate<String> filter) throws IOException {
|
||||
if (className == null || filter.test(className)) {
|
||||
return this.objectSourceClass;
|
||||
@@ -690,7 +690,7 @@ class ConfigurationClassParser {
|
||||
return new SourceClass(ClassUtils.forName(className, this.resourceLoader.getClassLoader()));
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new NestedIOException("Failed to load class [" + className + "]", ex);
|
||||
throw new org.springframework.core.NestedIOException("Failed to load class [" + className + "]", ex);
|
||||
}
|
||||
}
|
||||
return new SourceClass(this.metadataReaderFactory.getMetadataReader(className));
|
||||
@@ -1073,6 +1073,7 @@ class ConfigurationClassParser {
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private SourceClass getRelated(String className) throws IOException {
|
||||
if (this.source instanceof Class) {
|
||||
try {
|
||||
@@ -1082,7 +1083,7 @@ class ConfigurationClassParser {
|
||||
catch (ClassNotFoundException ex) {
|
||||
// Ignore -> fall back to ASM next, except for core java types.
|
||||
if (className.startsWith("java")) {
|
||||
throw new NestedIOException("Failed to load class [" + className + "]", ex);
|
||||
throw new org.springframework.core.NestedIOException("Failed to load class [" + className + "]", ex);
|
||||
}
|
||||
return new SourceClass(metadataReaderFactory.getMetadataReader(className));
|
||||
}
|
||||
|
||||
+3
-2
@@ -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.
|
||||
@@ -96,6 +96,8 @@ public class Jsr310DateTimeFormatAnnotationFormatterFactory extends EmbeddedValu
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Parser<?> getParser(DateTimeFormat annotation, Class<?> fieldType) {
|
||||
DateTimeFormatter formatter = getFormatter(annotation, fieldType);
|
||||
|
||||
List<String> resolvedFallbackPatterns = new ArrayList<>();
|
||||
for (String fallbackPattern : annotation.fallbackPatterns()) {
|
||||
String resolvedFallbackPattern = resolveEmbeddedValue(fallbackPattern);
|
||||
@@ -104,7 +106,6 @@ public class Jsr310DateTimeFormatAnnotationFormatterFactory extends EmbeddedValu
|
||||
}
|
||||
}
|
||||
|
||||
DateTimeFormatter formatter = getFormatter(annotation, fieldType);
|
||||
return new TemporalAccessorParser((Class<? extends TemporalAccessor>) fieldType,
|
||||
formatter, resolvedFallbackPatterns.toArray(new String[0]), annotation);
|
||||
}
|
||||
|
||||
+5
-4
@@ -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.
|
||||
@@ -71,7 +71,8 @@ public final class TemporalAccessorParser implements Parser<TemporalAccessor> {
|
||||
}
|
||||
|
||||
TemporalAccessorParser(Class<? extends TemporalAccessor> temporalAccessorType, DateTimeFormatter formatter,
|
||||
@Nullable String[] fallbackPatterns, @Nullable Object source) {
|
||||
@Nullable String[] fallbackPatterns, @Nullable Object source) {
|
||||
|
||||
this.temporalAccessorType = temporalAccessorType;
|
||||
this.formatter = formatter;
|
||||
this.fallbackPatterns = fallbackPatterns;
|
||||
@@ -100,8 +101,8 @@ public final class TemporalAccessorParser implements Parser<TemporalAccessor> {
|
||||
}
|
||||
if (this.source != null) {
|
||||
throw new DateTimeParseException(
|
||||
String.format("Unable to parse date time value \"%s\" using configuration from %s", text, this.source),
|
||||
text, ex.getErrorIndex(), ex);
|
||||
String.format("Unable to parse date time value \"%s\" using configuration from %s", text, this.source),
|
||||
text, ex.getErrorIndex(), ex);
|
||||
}
|
||||
// else rethrow original exception
|
||||
throw ex;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Package providing integration of
|
||||
* <a href="http://www.beanshell.org">BeanShell</a>
|
||||
* <a href="https://beanshell.github.io/">BeanShell</a>
|
||||
* (and <a href="https://code.google.com/p/beanshell2/">BeanShell2</a>)
|
||||
* into Spring's scripting infrastructure.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Package providing integration of
|
||||
* <a href="http://www.groovy-lang.org/">Groovy</a>
|
||||
* <a href="https://www.groovy-lang.org/">Groovy</a>
|
||||
* into Spring's scripting infrastructure.
|
||||
*/
|
||||
@NonNullApi
|
||||
|
||||
+7
@@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.support.NoOpCache;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -84,4 +85,10 @@ class LoggingCacheErrorHandlerTests {
|
||||
verify(this.logger).warn("Cache 'NOOP' failed to get entry with key 'enigma'", exception);
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorWithLoggerName() {
|
||||
assertThatCode(() -> new LoggingCacheErrorHandler("org.apache.commons.logging.Log", true))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.context.annotation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for gh-29105.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class Gh29105Tests {
|
||||
|
||||
@Test
|
||||
void beanProviderWithParentContextReuseOrder() {
|
||||
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
|
||||
parent.register(DefaultConfiguration.class);
|
||||
parent.register(CustomConfiguration.class);
|
||||
parent.refresh();
|
||||
|
||||
AnnotationConfigApplicationContext child = new AnnotationConfigApplicationContext();
|
||||
child.setParent(parent);
|
||||
child.register(DefaultConfiguration.class);
|
||||
child.refresh();
|
||||
|
||||
List<Class<?>> orderedTypes = child.getBeanProvider(MyService.class)
|
||||
.orderedStream().map(Object::getClass).collect(Collectors.toList());
|
||||
assertThat(orderedTypes).containsExactly(CustomService.class, DefaultService.class);
|
||||
child.close();
|
||||
}
|
||||
|
||||
|
||||
interface MyService {}
|
||||
|
||||
static class CustomService implements MyService {}
|
||||
|
||||
static class DefaultService implements MyService {}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class CustomConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(-1)
|
||||
CustomService customService() {
|
||||
return new CustomService();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class DefaultConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(0)
|
||||
DefaultService defaultService() {
|
||||
return new DefaultService();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -117,7 +117,7 @@ class ApplicationContextExpressionTests {
|
||||
ac.registerBeanDefinition("tb1", bd1);
|
||||
|
||||
GenericBeanDefinition bd2 = new GenericBeanDefinition();
|
||||
bd2.setBeanClassName("#{tb1.class.name}");
|
||||
bd2.setBeanClassName("#{tb1.class}");
|
||||
bd2.setScope("myScope");
|
||||
bd2.getPropertyValues().add("name", "{ XXX#{tb0.name}YYY#{mySpecialAttr}ZZZ }");
|
||||
bd2.getPropertyValues().add("age", "#{mySpecialAttr}");
|
||||
|
||||
@@ -46,11 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* {@code jmxremote_optional.jar} into your classpath, for example in the {@code lib/ext}
|
||||
* folder of your JVM.
|
||||
*
|
||||
* <p>See also:
|
||||
* <ul>
|
||||
* <li><a href="https://jira.spring.io/browse/SPR-8093">SPR-8093</a></li>
|
||||
* <li><a href="https://issuetracker.springsource.com/browse/EBR-349">EBR-349</a></li>
|
||||
* </ul>
|
||||
* <p>See also: <a href="https://jira.spring.io/browse/SPR-8093">SPR-8093</a>
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Juergen Hoeller
|
||||
|
||||
@@ -58,6 +58,7 @@ dependencies {
|
||||
testImplementation("org.xmlunit:xmlunit-matchers")
|
||||
testImplementation("io.projectreactor:reactor-test")
|
||||
testImplementation("io.projectreactor.tools:blockhound")
|
||||
testImplementation("com.squareup.okhttp3:mockwebserver")
|
||||
testFixturesImplementation("com.google.code.findbugs:jsr305")
|
||||
testFixturesImplementation("org.junit.platform:junit-platform-launcher")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api")
|
||||
|
||||
@@ -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 << 2) | returnSize} (argumentsSize is therefore equal to {@code
|
||||
* i >> 2}, and returnSize to {@code i & 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 << 2) | returnSize} (argumentsSize is therefore equal to {@code
|
||||
* i >> 2}, and returnSize to {@code i & 0x03}).
|
||||
*/
|
||||
public static int getArgumentsAndReturnSizes(final String methodDescriptor) {
|
||||
int argumentsSize = 1;
|
||||
|
||||
@@ -226,7 +226,7 @@ public final class BridgeMethodResolver {
|
||||
/**
|
||||
* Compare the signatures of the bridge method and the method which it bridges. If
|
||||
* the parameter and return types are the same, it is a 'visibility' bridge method
|
||||
* introduced in Java 6 to fix https://bugs.java.com/view_bug.do?bug_id=6342411.
|
||||
* introduced in Java 6 to fix https://bugs.openjdk.org/browse/JDK-6342411.
|
||||
* See also https://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html
|
||||
* @return whether signatures match as described
|
||||
*/
|
||||
|
||||
@@ -191,7 +191,8 @@ public final class CollectionFactory {
|
||||
else if (LinkedList.class == collectionType) {
|
||||
return new LinkedList<>();
|
||||
}
|
||||
else if (SortedSet.class == collectionType || NavigableSet.class == collectionType) {
|
||||
else if (TreeSet.class == collectionType || NavigableSet.class == collectionType
|
||||
|| SortedSet.class == collectionType) {
|
||||
return new TreeSet<>();
|
||||
}
|
||||
else if (EnumSet.class.isAssignableFrom(collectionType)) {
|
||||
|
||||
@@ -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.
|
||||
@@ -29,7 +29,6 @@ import org.springframework.lang.Nullable;
|
||||
* @since 2.0
|
||||
* @see NestedRuntimeException
|
||||
* @see NestedCheckedException
|
||||
* @see NestedIOException
|
||||
* @see org.springframework.web.util.NestedServletException
|
||||
*/
|
||||
public abstract class NestedExceptionUtils {
|
||||
|
||||
@@ -37,7 +37,9 @@ import org.springframework.lang.Nullable;
|
||||
* @see #printStackTrace
|
||||
* @see org.springframework.core.NestedCheckedException
|
||||
* @see org.springframework.core.NestedRuntimeException
|
||||
* @deprecated as of 5.3.23, in favor of using {@link IOException} directly
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("serial")
|
||||
public class NestedIOException extends IOException {
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
@@ -173,7 +173,7 @@ import java.lang.annotation.Target;
|
||||
* @author Sam Brannen
|
||||
* @since 4.2
|
||||
* @see MergedAnnotations
|
||||
* @see SynthesizedAnnotation
|
||||
* @see AnnotationUtils#isSynthesizedAnnotation(Annotation)
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
|
||||
+58
-8
@@ -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.
|
||||
@@ -433,6 +433,16 @@ public abstract class AnnotatedElementUtils {
|
||||
* single annotation and within annotation hierarchies.
|
||||
* <p>This method follows <em>get semantics</em> as described in the
|
||||
* {@linkplain AnnotatedElementUtils class-level javadoc}.
|
||||
* <p><strong>WARNING</strong>: if the supplied {@code containerType} is not
|
||||
* {@code null}, the search will be restricted to supporting only repeatable
|
||||
* annotations whose container is the supplied {@code containerType}. This
|
||||
* prevents the search from finding repeatable annotations declared as
|
||||
* meta-annotations on other types of repeatable annotations. If you need to
|
||||
* support such a use case, favor {@link #getMergedRepeatableAnnotations(AnnotatedElement, Class)}
|
||||
* over this method or alternatively use the {@link MergedAnnotations} API
|
||||
* directly in conjunction with {@link RepeatableContainers} that are
|
||||
* {@linkplain RepeatableContainers#and(Class, Class) composed} to support
|
||||
* multiple repeatable annotation types.
|
||||
* @param element the annotated element (never {@code null})
|
||||
* @param annotationType the annotation type to find (never {@code null})
|
||||
* @param containerType the type of the container that holds the annotations;
|
||||
@@ -725,6 +735,16 @@ public abstract class AnnotatedElementUtils {
|
||||
* single annotation and within annotation hierarchies.
|
||||
* <p>This method follows <em>find semantics</em> as described in the
|
||||
* {@linkplain AnnotatedElementUtils class-level javadoc}.
|
||||
* <p><strong>WARNING</strong>: if the supplied {@code containerType} is not
|
||||
* {@code null}, the search will be restricted to supporting only repeatable
|
||||
* annotations whose container is the supplied {@code containerType}. This
|
||||
* prevents the search from finding repeatable annotations declared as
|
||||
* meta-annotations on other types of repeatable annotations. If you need to
|
||||
* support such a use case, favor {@link #findMergedRepeatableAnnotations(AnnotatedElement, Class)}
|
||||
* over this method or alternatively use the {@link MergedAnnotations} API
|
||||
* directly in conjunction with {@link RepeatableContainers} that are
|
||||
* {@linkplain RepeatableContainers#and(Class, Class) composed} to support
|
||||
* multiple repeatable annotation types.
|
||||
* @param element the annotated element (never {@code null})
|
||||
* @param annotationType the annotation type to find (never {@code null})
|
||||
* @param containerType the type of the container that holds the annotations;
|
||||
@@ -756,7 +776,23 @@ public abstract class AnnotatedElementUtils {
|
||||
private static MergedAnnotations getRepeatableAnnotations(AnnotatedElement element,
|
||||
@Nullable Class<? extends Annotation> containerType, Class<? extends Annotation> annotationType) {
|
||||
|
||||
RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType);
|
||||
RepeatableContainers repeatableContainers;
|
||||
if (containerType == null) {
|
||||
// Invoke RepeatableContainers.of() in order to adhere to the contract of
|
||||
// getMergedRepeatableAnnotations() which states that an IllegalArgumentException
|
||||
// will be thrown if the container cannot be resolved.
|
||||
//
|
||||
// In any case, we use standardRepeatables() in order to support repeatable
|
||||
// annotations on other types of repeatable annotations (i.e., nested repeatable
|
||||
// annotation types).
|
||||
//
|
||||
// See https://github.com/spring-projects/spring-framework/issues/20279
|
||||
RepeatableContainers.of(annotationType, null);
|
||||
repeatableContainers = RepeatableContainers.standardRepeatables();
|
||||
}
|
||||
else {
|
||||
repeatableContainers = RepeatableContainers.of(annotationType, containerType);
|
||||
}
|
||||
return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS, repeatableContainers);
|
||||
}
|
||||
|
||||
@@ -767,7 +803,23 @@ public abstract class AnnotatedElementUtils {
|
||||
private static MergedAnnotations findRepeatableAnnotations(AnnotatedElement element,
|
||||
@Nullable Class<? extends Annotation> containerType, Class<? extends Annotation> annotationType) {
|
||||
|
||||
RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType);
|
||||
RepeatableContainers repeatableContainers;
|
||||
if (containerType == null) {
|
||||
// Invoke RepeatableContainers.of() in order to adhere to the contract of
|
||||
// findMergedRepeatableAnnotations() which states that an IllegalArgumentException
|
||||
// will be thrown if the container cannot be resolved.
|
||||
//
|
||||
// In any case, we use standardRepeatables() in order to support repeatable
|
||||
// annotations on other types of repeatable annotations (i.e., nested repeatable
|
||||
// annotation types).
|
||||
//
|
||||
// See https://github.com/spring-projects/spring-framework/issues/20279
|
||||
RepeatableContainers.of(annotationType, null);
|
||||
repeatableContainers = RepeatableContainers.standardRepeatables();
|
||||
}
|
||||
else {
|
||||
repeatableContainers = RepeatableContainers.of(annotationType, containerType);
|
||||
}
|
||||
return MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY, repeatableContainers);
|
||||
}
|
||||
|
||||
@@ -777,8 +829,7 @@ public abstract class AnnotatedElementUtils {
|
||||
}
|
||||
|
||||
private static <A extends Annotation> Comparator<MergedAnnotation<A>> highAggregateIndexesFirst() {
|
||||
return Comparator.<MergedAnnotation<A>> comparingInt(
|
||||
MergedAnnotation::getAggregateIndex).reversed();
|
||||
return Comparator.<MergedAnnotation<A>> comparingInt(MergedAnnotation::getAggregateIndex).reversed();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -788,13 +839,12 @@ public abstract class AnnotatedElementUtils {
|
||||
if (!annotation.isPresent()) {
|
||||
return null;
|
||||
}
|
||||
return annotation.asAnnotationAttributes(
|
||||
Adapt.values(classValuesAsString, nestedAnnotationsAsMap));
|
||||
return annotation.asAnnotationAttributes(Adapt.values(classValuesAsString, nestedAnnotationsAsMap));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adapted {@link AnnotatedElement} that hold specific annotations.
|
||||
* Adapted {@link AnnotatedElement} that holds specific annotations.
|
||||
*/
|
||||
private static class AnnotatedElementForAnnotations implements AnnotatedElement {
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
@@ -25,7 +25,7 @@ import org.springframework.core.NestedRuntimeException;
|
||||
* @author Sam Brannen
|
||||
* @since 4.2
|
||||
* @see AnnotationUtils
|
||||
* @see SynthesizedAnnotation
|
||||
* @see AnnotationUtils#isSynthesizedAnnotation(java.lang.annotation.Annotation)
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class AnnotationConfigurationException extends NestedRuntimeException {
|
||||
|
||||
+3
-4
@@ -32,7 +32,6 @@ import java.util.Set;
|
||||
import org.springframework.core.annotation.AnnotationTypeMapping.MirrorSets.MirrorSet;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -241,7 +240,7 @@ final class AnnotationTypeMapping {
|
||||
mapping.claimedAliases.addAll(aliases);
|
||||
if (mapping.annotation != null) {
|
||||
int[] resolvedMirrors = mapping.mirrorSets.resolve(null,
|
||||
mapping.annotation, ReflectionUtils::invokeMethod);
|
||||
mapping.annotation, AnnotationUtils::invokeAnnotationMethod);
|
||||
for (int i = 0; i < mapping.attributes.size(); i++) {
|
||||
if (aliases.contains(mapping.attributes.get(i))) {
|
||||
this.annotationValueMappings[attributeIndex] = resolvedMirrors[i];
|
||||
@@ -505,7 +504,7 @@ final class AnnotationTypeMapping {
|
||||
if (source == this && metaAnnotationsOnly) {
|
||||
return null;
|
||||
}
|
||||
return ReflectionUtils.invokeMethod(source.attributes.get(mappedIndex), source.annotation);
|
||||
return AnnotationUtils.invokeAnnotationMethod(source.attributes.get(mappedIndex), source.annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -594,7 +593,7 @@ final class AnnotationTypeMapping {
|
||||
AttributeMethods attributes = AttributeMethods.forAnnotationType(annotation.annotationType());
|
||||
for (int i = 0; i < attributes.size(); i++) {
|
||||
Method attribute = attributes.get(i);
|
||||
Object value1 = ReflectionUtils.invokeMethod(attribute, annotation);
|
||||
Object value1 = AnnotationUtils.invokeAnnotationMethod(attribute, annotation);
|
||||
Object value2;
|
||||
if (extractedValue instanceof TypeMappedAnnotation) {
|
||||
value2 = ((TypeMappedAnnotation<?>) extractedValue).getValue(attribute.getName()).orElse(null);
|
||||
|
||||
+1
-1
@@ -160,7 +160,7 @@ final class AnnotationTypeMappings {
|
||||
* @param index the index to return
|
||||
* @return the {@link AnnotationTypeMapping}
|
||||
* @throws IndexOutOfBoundsException if the index is out of range
|
||||
* (<tt>index < 0 || index >= size()</tt>)
|
||||
* ({@code index < 0 || index >= size()})
|
||||
*/
|
||||
AnnotationTypeMapping get(int index) {
|
||||
return this.mappings.get(index);
|
||||
|
||||
+42
-10
@@ -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.
|
||||
@@ -19,9 +19,10 @@ package org.springframework.core.annotation;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -1052,23 +1053,42 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
try {
|
||||
Method method = annotation.annotationType().getDeclaredMethod(attributeName);
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
return method.invoke(annotation);
|
||||
return invokeAnnotationMethod(method, annotation);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
return null;
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
rethrowAnnotationConfigurationException(ex.getTargetException());
|
||||
throw new IllegalStateException("Could not obtain value for annotation attribute '" +
|
||||
attributeName + "' in " + annotation, ex);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
rethrowAnnotationConfigurationException(ex);
|
||||
handleIntrospectionFailure(annotation.getClass(), ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the supplied annotation attribute {@link Method} on the supplied
|
||||
* {@link Annotation}.
|
||||
* <p>An attempt will first be made to invoke the method via the annotation's
|
||||
* {@link InvocationHandler} (if the annotation instance is a JDK dynamic proxy).
|
||||
* If that fails, an attempt will be made to invoke the method via reflection.
|
||||
* @param method the method to invoke
|
||||
* @param annotation the annotation on which to invoke the method
|
||||
* @return the value returned from the method invocation
|
||||
* @since 5.3.24
|
||||
*/
|
||||
static Object invokeAnnotationMethod(Method method, Object annotation) {
|
||||
if (Proxy.isProxyClass(annotation.getClass())) {
|
||||
try {
|
||||
InvocationHandler handler = Proxy.getInvocationHandler(annotation);
|
||||
return handler.invoke(annotation, method, null);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// ignore and fall back to reflection below
|
||||
}
|
||||
}
|
||||
return ReflectionUtils.invokeMethod(method, annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the supplied throwable is an {@link AnnotationConfigurationException},
|
||||
* it will be cast to an {@code AnnotationConfigurationException} and thrown,
|
||||
@@ -1188,7 +1208,7 @@ public abstract class AnnotationUtils {
|
||||
public static <A extends Annotation> A synthesizeAnnotation(
|
||||
A annotation, @Nullable AnnotatedElement annotatedElement) {
|
||||
|
||||
if (annotation instanceof SynthesizedAnnotation || AnnotationFilter.PLAIN.matches(annotation)) {
|
||||
if (isSynthesizedAnnotation(annotation) || AnnotationFilter.PLAIN.matches(annotation)) {
|
||||
return annotation;
|
||||
}
|
||||
return MergedAnnotation.from(annotatedElement, annotation).synthesize();
|
||||
@@ -1282,6 +1302,18 @@ public abstract class AnnotationUtils {
|
||||
return synthesized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the supplied {@link Annotation} has been <em>synthesized</em>
|
||||
* by Spring (i.e. wrapped in a dynamic proxy) with additional functionality
|
||||
* such as attribute alias handling.
|
||||
* @param annotation the annotation to check
|
||||
* @return {@code true} if the supplied annotation is a synthesized annotation
|
||||
* @since 5.3.23
|
||||
*/
|
||||
public static boolean isSynthesizedAnnotation(@Nullable Annotation annotation) {
|
||||
return (annotation instanceof SynthesizedAnnotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the internal annotation metadata cache.
|
||||
* @since 4.3.15
|
||||
|
||||
@@ -109,7 +109,7 @@ final class AttributeMethods {
|
||||
for (int i = 0; i < size(); i++) {
|
||||
if (canThrowTypeNotPresentException(i)) {
|
||||
try {
|
||||
get(i).invoke(annotation);
|
||||
AnnotationUtils.invokeAnnotationMethod(get(i), annotation);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
return false;
|
||||
@@ -134,7 +134,7 @@ final class AttributeMethods {
|
||||
for (int i = 0; i < size(); i++) {
|
||||
if (canThrowTypeNotPresentException(i)) {
|
||||
try {
|
||||
get(i).invoke(annotation);
|
||||
AnnotationUtils.invokeAnnotationMethod(get(i), annotation);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new IllegalStateException("Could not obtain annotation attribute value for " +
|
||||
@@ -168,7 +168,7 @@ final class AttributeMethods {
|
||||
* @param index the index of the attribute to return
|
||||
* @return the attribute method
|
||||
* @throws IndexOutOfBoundsException if the index is out of range
|
||||
* (<tt>index < 0 || index >= size()</tt>)
|
||||
* ({@code index < 0 || index >= size()})
|
||||
*/
|
||||
Method get(int index) {
|
||||
return this.attributeMethods[index];
|
||||
|
||||
+12
-8
@@ -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.
|
||||
@@ -25,7 +25,6 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Strategy used to determine annotations that act as containers for other
|
||||
@@ -39,6 +38,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
* <p>To completely disable repeatable support use {@link #none()}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @since 5.2
|
||||
*/
|
||||
public abstract class RepeatableContainers {
|
||||
@@ -101,15 +101,19 @@ public abstract class RepeatableContainers {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link RepeatableContainers} instance that uses a defined
|
||||
* container and repeatable type.
|
||||
* @param repeatable the contained repeatable annotation
|
||||
* @param container the container annotation or {@code null}. If specified,
|
||||
* Create a {@link RepeatableContainers} instance that uses predefined
|
||||
* repeatable and container types.
|
||||
* @param repeatable the contained repeatable annotation type
|
||||
* @param container the container annotation type or {@code null}. If specified,
|
||||
* this annotation must declare a {@code value} attribute returning an array
|
||||
* of repeatable annotations. If not specified, the container will be
|
||||
* deduced by inspecting the {@code @Repeatable} annotation on
|
||||
* {@code repeatable}.
|
||||
* @return a {@link RepeatableContainers} instance
|
||||
* @throws IllegalArgumentException if the supplied container type is
|
||||
* {@code null} and the annotation type is not a repeatable annotation
|
||||
* @throws AnnotationConfigurationException if the supplied container type
|
||||
* is not a properly configured container for a repeatable annotation
|
||||
*/
|
||||
public static RepeatableContainers of(
|
||||
Class<? extends Annotation> repeatable, @Nullable Class<? extends Annotation> container) {
|
||||
@@ -148,7 +152,7 @@ public abstract class RepeatableContainers {
|
||||
Annotation[] findRepeatedAnnotations(Annotation annotation) {
|
||||
Method method = getRepeatedAnnotationsMethod(annotation.annotationType());
|
||||
if (method != null) {
|
||||
return (Annotation[]) ReflectionUtils.invokeMethod(method, annotation);
|
||||
return (Annotation[]) AnnotationUtils.invokeAnnotationMethod(method, annotation);
|
||||
}
|
||||
return super.findRepeatedAnnotations(annotation);
|
||||
}
|
||||
@@ -235,7 +239,7 @@ public abstract class RepeatableContainers {
|
||||
@Nullable
|
||||
Annotation[] findRepeatedAnnotations(Annotation annotation) {
|
||||
if (this.container.isAssignableFrom(annotation.annotationType())) {
|
||||
return (Annotation[]) ReflectionUtils.invokeMethod(this.valueMethod, annotation);
|
||||
return (Annotation[]) AnnotationUtils.invokeAnnotationMethod(this.valueMethod, annotation);
|
||||
}
|
||||
return super.findRepeatedAnnotations(annotation);
|
||||
}
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
|
||||
for (int i = 0; i < this.attributes.size(); i++) {
|
||||
Method attribute = this.attributes.get(i);
|
||||
Object thisValue = getAttributeValue(attribute);
|
||||
Object otherValue = ReflectionUtils.invokeMethod(attribute, other);
|
||||
Object otherValue = AnnotationUtils.invokeAnnotationMethod(attribute, other);
|
||||
if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+8
-8
@@ -33,7 +33,6 @@ import java.util.function.Predicate;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link MergedAnnotation} that adapts attributes from a root annotation by
|
||||
@@ -43,9 +42,9 @@ import org.springframework.util.ReflectionUtils;
|
||||
* {@code BiFunction}. This allows various different annotation models to be
|
||||
* supported by the same class. For example, the attributes source might be an
|
||||
* actual {@link Annotation} instance where methods on the annotation instance
|
||||
* are {@linkplain ReflectionUtils#invokeMethod(Method, Object) invoked} to extract
|
||||
* values. Equally, the source could be a simple {@link Map} with values
|
||||
* extracted using {@link Map#get(Object)}.
|
||||
* are {@linkplain AnnotationUtils#invokeAnnotationMethod(Method, Object) invoked}
|
||||
* to extract values. Similarly, the source could be a simple {@link Map} with
|
||||
* values extracted using {@link Map#get(Object)}.
|
||||
*
|
||||
* <p>Extracted root attribute values must be compatible with the attribute
|
||||
* return type, namely:
|
||||
@@ -434,7 +433,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
|
||||
}
|
||||
if (value == null) {
|
||||
Method attribute = this.mapping.getAttributes().get(attributeIndex);
|
||||
value = ReflectionUtils.invokeMethod(attribute, this.mapping.getAnnotation());
|
||||
value = AnnotationUtils.invokeAnnotationMethod(attribute, this.mapping.getAnnotation());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -555,7 +554,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
|
||||
|
||||
private ValueExtractor getValueExtractor(Object value) {
|
||||
if (value instanceof Annotation) {
|
||||
return ReflectionUtils::invokeMethod;
|
||||
return AnnotationUtils::invokeAnnotationMethod;
|
||||
}
|
||||
if (value instanceof Map) {
|
||||
return TypeMappedAnnotation::extractFromMap;
|
||||
@@ -615,7 +614,8 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
|
||||
static <A extends Annotation> MergedAnnotation<A> from(@Nullable Object source, A annotation) {
|
||||
Assert.notNull(annotation, "Annotation must not be null");
|
||||
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotation.annotationType());
|
||||
return new TypeMappedAnnotation<>(mappings.get(0), null, source, annotation, ReflectionUtils::invokeMethod, 0);
|
||||
return new TypeMappedAnnotation<>(
|
||||
mappings.get(0), null, source, annotation, AnnotationUtils::invokeAnnotationMethod, 0);
|
||||
}
|
||||
|
||||
static <A extends Annotation> MergedAnnotation<A> of(
|
||||
@@ -649,7 +649,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
|
||||
int aggregateIndex, IntrospectionFailureLogger logger) {
|
||||
|
||||
return createIfPossible(mapping, source, annotation,
|
||||
ReflectionUtils::invokeMethod, aggregateIndex, logger);
|
||||
AnnotationUtils::invokeAnnotationMethod, aggregateIndex, logger);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -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.
|
||||
@@ -27,7 +27,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.MimeType;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link Decoder} implementations.
|
||||
* Abstract base class for {@link Encoder} implementations.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Arjen Poutsma
|
||||
|
||||
@@ -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.
|
||||
@@ -238,7 +238,7 @@ public class TypeDescriptor implements Serializable {
|
||||
* <p>As of Spring Framework 4.2, this method supports arbitrary levels
|
||||
* of meta-annotations.
|
||||
* @param annotationType the annotation type
|
||||
* @return <tt>true</tt> if the annotation is present
|
||||
* @return {@code true} if the annotation is present
|
||||
*/
|
||||
public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
|
||||
if (this.annotatedElement.isEmpty()) {
|
||||
@@ -273,8 +273,8 @@ public class TypeDescriptor implements Serializable {
|
||||
* On the other hand, {@code valueOf(Number.class).isAssignableTo(valueOf(Integer.class))}
|
||||
* returns {@code false} because, while all Integers are Numbers, not all Numbers are Integers.
|
||||
* <p>For arrays, collections, and maps, element and key/value types are checked if declared.
|
||||
* For example, a List<String> field value is assignable to a Collection<CharSequence>
|
||||
* field, but List<Number> is not assignable to List<Integer>.
|
||||
* For example, a {@code List<String>} field value is assignable to a {@code Collection<CharSequence>}
|
||||
* field, but {@code List<Number>} is not assignable to {@code List<Integer>}.
|
||||
* @return {@code true} if this type is assignable to the type represented by the provided
|
||||
* type descriptor
|
||||
* @see #getObjectType()
|
||||
@@ -576,7 +576,7 @@ public class TypeDescriptor implements Serializable {
|
||||
/**
|
||||
* Create a new type descriptor from a {@link java.util.Map} type.
|
||||
* <p>Useful for converting to typed Maps.
|
||||
* <p>For example, a Map<String, String> could be converted to a Map<Id, EmailAddress>
|
||||
* <p>For example, a {@code Map<String, String>} could be converted to a {@code Map<Id, EmailAddress>}
|
||||
* by converting to a targetType built with this method:
|
||||
* The method call to construct such a TypeDescriptor would look something like:
|
||||
* <pre class="code">
|
||||
|
||||
+2
-2
@@ -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.
|
||||
@@ -104,7 +104,7 @@ public class ConvertingComparator<S, T> implements Comparator<S> {
|
||||
|
||||
|
||||
/**
|
||||
* Adapts a {@link ConversionService} and <tt>targetType</tt> to a {@link Converter}.
|
||||
* Adapts a {@link ConversionService} and {@code targetType} to a {@link Converter}.
|
||||
*/
|
||||
private static class ConversionServiceConverter<S, T> implements Converter<S, T> {
|
||||
|
||||
|
||||
+13
-6
@@ -57,6 +57,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
HttpURLConnection httpCon =
|
||||
(con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
|
||||
if (httpCon != null) {
|
||||
httpCon.setRequestMethod("HEAD");
|
||||
int code = httpCon.getResponseCode();
|
||||
if (code == HttpURLConnection.HTTP_OK) {
|
||||
return true;
|
||||
@@ -108,6 +109,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
customizeConnection(con);
|
||||
if (con instanceof HttpURLConnection) {
|
||||
HttpURLConnection httpCon = (HttpURLConnection) con;
|
||||
httpCon.setRequestMethod("HEAD");
|
||||
int code = httpCon.getResponseCode();
|
||||
if (code != HttpURLConnection.HTTP_OK) {
|
||||
httpCon.disconnect();
|
||||
@@ -245,6 +247,10 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
// Try a URL connection content-length header
|
||||
URLConnection con = url.openConnection();
|
||||
customizeConnection(con);
|
||||
if (con instanceof HttpURLConnection) {
|
||||
HttpURLConnection httpCon = (HttpURLConnection) con;
|
||||
httpCon.setRequestMethod("HEAD");
|
||||
}
|
||||
return con.getContentLengthLong();
|
||||
}
|
||||
}
|
||||
@@ -270,6 +276,10 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
// Try a URL connection last-modified header
|
||||
URLConnection con = url.openConnection();
|
||||
customizeConnection(con);
|
||||
if (con instanceof HttpURLConnection) {
|
||||
HttpURLConnection httpCon = (HttpURLConnection) con;
|
||||
httpCon.setRequestMethod("HEAD");
|
||||
}
|
||||
long lastModified = con.getLastModified();
|
||||
if (fileCheck && lastModified == 0 && con.getContentLengthLong() <= 0) {
|
||||
throw new FileNotFoundException(getDescription() +
|
||||
@@ -279,8 +289,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize the given {@link URLConnection}, obtained in the course of an
|
||||
* {@link #exists()}, {@link #contentLength()} or {@link #lastModified()} call.
|
||||
* Customize the given {@link URLConnection} before fetching the resource.
|
||||
* <p>Calls {@link ResourceUtils#useCachesIfNecessary(URLConnection)} and
|
||||
* delegates to {@link #customizeConnection(HttpURLConnection)} if possible.
|
||||
* Can be overridden in subclasses.
|
||||
@@ -295,14 +304,12 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize the given {@link HttpURLConnection}, obtained in the course of an
|
||||
* {@link #exists()}, {@link #contentLength()} or {@link #lastModified()} call.
|
||||
* <p>Sets request method "HEAD" by default. Can be overridden in subclasses.
|
||||
* Customize the given {@link HttpURLConnection} before fetching the resource.
|
||||
* <p>Can be overridden in subclasses for configuring request headers and timeouts.
|
||||
* @param con the HttpURLConnection to customize
|
||||
* @throws IOException if thrown from HttpURLConnection methods
|
||||
*/
|
||||
protected void customizeConnection(HttpURLConnection con) throws IOException {
|
||||
con.setRequestMethod("HEAD");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import java.nio.channels.ReadableByteChannel;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.NestedIOException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
@@ -119,13 +118,14 @@ public abstract class AbstractResource implements Resource {
|
||||
* by {@link #getURL()}.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public URI getURI() throws IOException {
|
||||
URL url = getURL();
|
||||
try {
|
||||
return ResourceUtils.toURI(url);
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
throw new NestedIOException("Invalid URI [" + url + "]", ex);
|
||||
throw new org.springframework.core.NestedIOException("Invalid URI [" + url + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import java.net.URLConnection;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -183,7 +182,7 @@ public class UrlResource extends AbstractFileResolvingResource {
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
URLConnection con = this.url.openConnection();
|
||||
ResourceUtils.useCachesIfNecessary(con);
|
||||
customizeConnection(con);
|
||||
try {
|
||||
return con.getInputStream();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -22,7 +22,6 @@ import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
|
||||
import org.springframework.core.NestedIOException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -72,22 +71,24 @@ public class VfsResource extends AbstractResource {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public URL getURL() throws IOException {
|
||||
try {
|
||||
return VfsUtils.getURL(this.resource);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new NestedIOException("Failed to obtain URL for file " + this.resource, ex);
|
||||
throw new org.springframework.core.NestedIOException("Failed to obtain URL for file " + this.resource, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public URI getURI() throws IOException {
|
||||
try {
|
||||
return VfsUtils.getURI(this.resource);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new NestedIOException("Failed to obtain URI for " + this.resource, ex);
|
||||
throw new org.springframework.core.NestedIOException("Failed to obtain URI for " + this.resource, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -38,9 +38,9 @@ import org.springframework.util.Assert;
|
||||
* the following invariant holds for the read and write positions, and the capacity:
|
||||
*
|
||||
* <blockquote>
|
||||
* <tt>0</tt> <tt><=</tt>
|
||||
* <i>readPosition</i> <tt><=</tt>
|
||||
* <i>writePosition</i> <tt><=</tt>
|
||||
* {@code 0} {@code <=}
|
||||
* <i>readPosition</i> {@code <=}
|
||||
* <i>writePosition</i> {@code <=}
|
||||
* <i>capacity</i>
|
||||
* </blockquote>
|
||||
*
|
||||
|
||||
@@ -177,7 +177,7 @@ public abstract class DataBufferUtils {
|
||||
if (options.length > 0) {
|
||||
for (OpenOption option : options) {
|
||||
Assert.isTrue(!(option == StandardOpenOption.APPEND || option == StandardOpenOption.WRITE),
|
||||
"'" + option + "' not allowed");
|
||||
() -> "'" + option + "' not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,7 +366,8 @@ public abstract class DataBufferUtils {
|
||||
sink.onDispose(() -> closeChannel(channel));
|
||||
write(source, channel).subscribe(DataBufferUtils::release,
|
||||
sink::error,
|
||||
sink::success);
|
||||
sink::success,
|
||||
Context.of(sink.contextView()));
|
||||
}
|
||||
catch (IOException ex) {
|
||||
sink.error(ex);
|
||||
@@ -694,7 +695,7 @@ public abstract class DataBufferUtils {
|
||||
|
||||
@Override
|
||||
public byte[] delimiter() {
|
||||
Assert.state(this.longestDelimiter != NO_DELIMITER, "Illegal state!");
|
||||
Assert.state(this.longestDelimiter != NO_DELIMITER, "'delimiter' not set");
|
||||
return this.longestDelimiter;
|
||||
}
|
||||
|
||||
|
||||
+3
-4
@@ -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.
|
||||
@@ -21,7 +21,6 @@ import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
|
||||
import org.springframework.core.ConfigurableObjectInputStream;
|
||||
import org.springframework.core.NestedIOException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -65,14 +64,14 @@ public class DefaultDeserializer implements Deserializer<Object> {
|
||||
* @see ObjectInputStream#readObject()
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("resource")
|
||||
@SuppressWarnings("deprecation")
|
||||
public Object deserialize(InputStream inputStream) throws IOException {
|
||||
ObjectInputStream objectInputStream = new ConfigurableObjectInputStream(inputStream, this.classLoader);
|
||||
try {
|
||||
return objectInputStream.readObject();
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new NestedIOException("Failed to deserialize object type", ex);
|
||||
throw new org.springframework.core.NestedIOException("Failed to deserialize object type", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.springframework.asm.ClassReader;
|
||||
import org.springframework.core.NestedIOException;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.ClassMetadata;
|
||||
@@ -51,13 +50,14 @@ final class SimpleMetadataReader implements MetadataReader {
|
||||
this.annotationMetadata = visitor.getMetadata();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static ClassReader getClassReader(Resource resource) throws IOException {
|
||||
try (InputStream is = resource.getInputStream()) {
|
||||
try {
|
||||
return new ClassReader(is);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new NestedIOException("ASM ClassReader failed to parse class file - " +
|
||||
throw new org.springframework.core.NestedIOException("ASM ClassReader failed to parse class file - " +
|
||||
"probably due to a new Java class file version that isn't supported yet: " + resource, ex);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilte
|
||||
}
|
||||
catch (IOException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Could not read super class [" + metadata.getSuperClassName() +
|
||||
logger.debug("Could not read superclass [" + metadata.getSuperClassName() +
|
||||
"] of type-filtered class [" + metadata.getClassName() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -638,7 +638,7 @@ public class AntPathMatcher implements PathMatcher {
|
||||
/**
|
||||
* Tests whether a string matches against a pattern via a {@link Pattern}.
|
||||
* <p>The pattern may contain special characters: '*' means zero or more characters; '?' means one and
|
||||
* only one character; '{' and '}' indicate a URI template pattern. For example <tt>/users/{user}</tt>.
|
||||
* only one character; '{' and '}' indicate a URI template pattern. For example {@code /users/{user}}.
|
||||
*/
|
||||
protected static class AntPathStringMatcher {
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -85,7 +85,7 @@ public abstract class CollectionUtils {
|
||||
* @see #newLinkedHashMap(int)
|
||||
*/
|
||||
public static <K, V> HashMap<K, V> newHashMap(int expectedSize) {
|
||||
return new HashMap<>((int) (expectedSize / DEFAULT_LOAD_FACTOR), DEFAULT_LOAD_FACTOR);
|
||||
return new HashMap<>(computeMapInitialCapacity(expectedSize), DEFAULT_LOAD_FACTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +102,11 @@ public abstract class CollectionUtils {
|
||||
* @see #newHashMap(int)
|
||||
*/
|
||||
public static <K, V> LinkedHashMap<K, V> newLinkedHashMap(int expectedSize) {
|
||||
return new LinkedHashMap<>((int) (expectedSize / DEFAULT_LOAD_FACTOR), DEFAULT_LOAD_FACTOR);
|
||||
return new LinkedHashMap<>(computeMapInitialCapacity(expectedSize), DEFAULT_LOAD_FACTOR);
|
||||
}
|
||||
|
||||
private static int computeMapInitialCapacity(int expectedSize) {
|
||||
return (int) Math.ceil(expectedSize / (double) DEFAULT_LOAD_FACTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -150,7 +150,7 @@ public class FastByteArrayOutputStream extends OutputStream {
|
||||
|
||||
/**
|
||||
* Convert the buffer's contents into a string decoding bytes using the
|
||||
* platform's default character set. The length of the new <tt>String</tt>
|
||||
* platform's default character set. The length of the new {@code String}
|
||||
* is a function of the character set, and hence may not be equal to the
|
||||
* size of the buffer.
|
||||
* <p>This method always replaces malformed-input and unmappable-character
|
||||
|
||||
+3
-1
@@ -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.
|
||||
@@ -114,6 +114,7 @@ public class SettableListenableFuture<T> implements ListenableFuture<T> {
|
||||
* {@link java.util.concurrent.CancellationException} if the future has been cancelled.
|
||||
* @return the value associated with this future
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public T get() throws InterruptedException, ExecutionException {
|
||||
return this.settableTask.get();
|
||||
@@ -129,6 +130,7 @@ public class SettableListenableFuture<T> implements ListenableFuture<T> {
|
||||
* @param unit the unit of the timeout argument
|
||||
* @return the value associated with this future
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return this.settableTask.get(timeout, unit);
|
||||
|
||||
@@ -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,6 @@ import java.util.SortedSet;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
@@ -156,7 +155,7 @@ class CollectionFactoryTests {
|
||||
@Test
|
||||
void createApproximateCollectionFromEmptyHashSet() {
|
||||
Collection<String> set = createApproximateCollection(new HashSet<String>(), 2);
|
||||
Assertions.assertThat(set).isEmpty();
|
||||
assertThat(set).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-7
@@ -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.
|
||||
@@ -33,7 +33,7 @@ class AnnotationBackCompatibilityTests {
|
||||
|
||||
@Test
|
||||
void multiplRoutesToMetaAnnotation() {
|
||||
Class<WithMetaMetaTestAnnotation1AndMetaTestAnnotation2> source = WithMetaMetaTestAnnotation1AndMetaTestAnnotation2.class;
|
||||
Class<?> source = WithMetaMetaTestAnnotation1AndMetaTestAnnotation2.class;
|
||||
// Merged annotation chooses lowest depth
|
||||
MergedAnnotation<TestAnnotation> mergedAnnotation = MergedAnnotations.from(source).get(TestAnnotation.class);
|
||||
assertThat(mergedAnnotation.getString("value")).isEqualTo("testAndMetaTest");
|
||||
@@ -45,7 +45,7 @@ class AnnotationBackCompatibilityTests {
|
||||
@Test
|
||||
void defaultValue() {
|
||||
DefaultValueAnnotation synthesized = MergedAnnotations.from(WithDefaultValue.class).get(DefaultValueAnnotation.class).synthesize();
|
||||
assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertThat(AnnotationUtils.isSynthesizedAnnotation(synthesized)).as("synthesized annotation").isTrue();
|
||||
Object defaultValue = AnnotationUtils.getDefaultValue(synthesized, "enumValue");
|
||||
assertThat(defaultValue).isEqualTo(TestEnum.ONE);
|
||||
}
|
||||
@@ -60,14 +60,12 @@ class AnnotationBackCompatibilityTests {
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@TestAnnotation("metaTest")
|
||||
@interface MetaTestAnnotation {
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@TestAnnotation("testAndMetaTest")
|
||||
@MetaTestAnnotation
|
||||
@interface TestAndMetaTestAnnotation {
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -78,7 +76,6 @@ class AnnotationBackCompatibilityTests {
|
||||
@MetaMetaTestAnnotation
|
||||
@TestAndMetaTestAnnotation
|
||||
static class WithMetaMetaTestAnnotation1AndMetaTestAnnotation2 {
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -94,7 +91,6 @@ class AnnotationBackCompatibilityTests {
|
||||
|
||||
@DefaultValueAnnotation
|
||||
static class WithDefaultValue {
|
||||
|
||||
}
|
||||
|
||||
enum TestEnum {
|
||||
|
||||
+4
-4
@@ -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.
|
||||
@@ -45,7 +45,7 @@ class MergedAnnotationCollectorsTests {
|
||||
MergedAnnotationCollectors.toAnnotationSet());
|
||||
assertThat(set).isInstanceOf(LinkedHashSet.class).flatExtracting(
|
||||
TestAnnotation::value).containsExactly("a", "b", "c");
|
||||
assertThat(set).allMatch(SynthesizedAnnotation.class::isInstance);
|
||||
assertThat(set).allMatch(AnnotationUtils::isSynthesizedAnnotation);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -55,7 +55,7 @@ class MergedAnnotationCollectorsTests {
|
||||
assertThat(Arrays.stream(array).map(
|
||||
annotation -> ((TestAnnotation) annotation).value())).containsExactly("a",
|
||||
"b", "c");
|
||||
assertThat(array).allMatch(SynthesizedAnnotation.class::isInstance);
|
||||
assertThat(array).allMatch(AnnotationUtils::isSynthesizedAnnotation);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -64,7 +64,7 @@ class MergedAnnotationCollectorsTests {
|
||||
MergedAnnotationCollectors.toAnnotationArray(TestAnnotation[]::new));
|
||||
assertThat(Arrays.stream(array).map(TestAnnotation::value)).containsExactly("a",
|
||||
"b", "c");
|
||||
assertThat(array).allMatch(SynthesizedAnnotation.class::isInstance);
|
||||
assertThat(array).allMatch(AnnotationUtils::isSynthesizedAnnotation);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+36
-30
@@ -1395,9 +1395,10 @@ class MergedAnnotationsTests {
|
||||
RequestMapping synthesizedWebMapping = MergedAnnotation.from(webMapping).synthesize();
|
||||
RequestMapping synthesizedAgainWebMapping = MergedAnnotation.from(synthesizedWebMapping).synthesize();
|
||||
|
||||
assertThat(synthesizedWebMapping).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertThat(synthesizedAgainWebMapping).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(synthesizedWebMapping);
|
||||
assertSynthesized(synthesizedAgainWebMapping);
|
||||
assertThat(synthesizedWebMapping).isEqualTo(synthesizedAgainWebMapping);
|
||||
assertThat(synthesizedWebMapping).isSameAs(synthesizedAgainWebMapping);
|
||||
assertThat(synthesizedWebMapping.name()).isEqualTo("foo");
|
||||
assertThat(synthesizedWebMapping.path()).containsExactly("/test");
|
||||
assertThat(synthesizedWebMapping.value()).containsExactly("/test");
|
||||
@@ -1412,7 +1413,7 @@ class MergedAnnotationsTests {
|
||||
Id synthesizedId = MergedAnnotation.from(id).synthesize();
|
||||
assertThat(id).isEqualTo(synthesizedId);
|
||||
// It doesn't make sense to synthesize @Id since it declares zero attributes.
|
||||
assertThat(synthesizedId).isNotInstanceOf(SynthesizedAnnotation.class);
|
||||
assertNotSynthesized(synthesizedId);
|
||||
assertThat(id).isSameAs(synthesizedId);
|
||||
|
||||
GeneratedValue generatedValue = method.getAnnotation(GeneratedValue.class);
|
||||
@@ -1420,7 +1421,7 @@ class MergedAnnotationsTests {
|
||||
GeneratedValue synthesizedGeneratedValue = MergedAnnotation.from(generatedValue).synthesize();
|
||||
assertThat(generatedValue).isEqualTo(synthesizedGeneratedValue);
|
||||
// It doesn't make sense to synthesize @GeneratedValue since it declares zero attributes with aliases.
|
||||
assertThat(synthesizedGeneratedValue).isNotInstanceOf(SynthesizedAnnotation.class);
|
||||
assertNotSynthesized(synthesizedGeneratedValue);
|
||||
assertThat(generatedValue).isSameAs(synthesizedGeneratedValue);
|
||||
}
|
||||
|
||||
@@ -1430,19 +1431,19 @@ class MergedAnnotationsTests {
|
||||
MergedAnnotations mergedAnnotations = MergedAnnotations.from(directlyAnnotatedField);
|
||||
RootAnnotation rootAnnotation = mergedAnnotations.get(RootAnnotation.class).synthesize();
|
||||
assertThat(rootAnnotation.flag()).isFalse();
|
||||
assertThat(rootAnnotation).isNotInstanceOf(SynthesizedAnnotation.class);
|
||||
assertNotSynthesized(rootAnnotation);
|
||||
|
||||
Field metaAnnotatedField = ReflectionUtils.findField(DomainType.class, "metaAnnotated");
|
||||
mergedAnnotations = MergedAnnotations.from(metaAnnotatedField);
|
||||
rootAnnotation = mergedAnnotations.get(RootAnnotation.class).synthesize();
|
||||
assertThat(rootAnnotation.flag()).isTrue();
|
||||
assertThat(rootAnnotation).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(rootAnnotation);
|
||||
|
||||
Field metaMetaAnnotatedField = ReflectionUtils.findField(DomainType.class, "metaMetaAnnotated");
|
||||
mergedAnnotations = MergedAnnotations.from(metaMetaAnnotatedField);
|
||||
rootAnnotation = mergedAnnotations.get(RootAnnotation.class).synthesize();
|
||||
assertThat(rootAnnotation.flag()).isTrue();
|
||||
assertThat(rootAnnotation).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(rootAnnotation);
|
||||
}
|
||||
|
||||
@Test // gh-28704
|
||||
@@ -1450,10 +1451,10 @@ class MergedAnnotationsTests {
|
||||
MergedAnnotations mergedAnnotations = MergedAnnotations.from(SecurityConfig.class);
|
||||
|
||||
EnableWebSecurity enableWebSecurity = mergedAnnotations.get(EnableWebSecurity.class).synthesize();
|
||||
assertThat(enableWebSecurity).isNotInstanceOf(SynthesizedAnnotation.class);
|
||||
assertNotSynthesized(enableWebSecurity);
|
||||
|
||||
EnableGlobalAuthentication enableGlobalAuthentication = mergedAnnotations.get(EnableGlobalAuthentication.class).synthesize();
|
||||
assertThat(enableGlobalAuthentication).isNotInstanceOf(SynthesizedAnnotation.class);
|
||||
assertNotSynthesized(enableGlobalAuthentication);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1472,8 +1473,8 @@ class MergedAnnotationsTests {
|
||||
RequestMapping synthesizedWebMapping1 = mergedAnnotation1.synthesize();
|
||||
RequestMapping synthesizedWebMapping2 = MergedAnnotation.from(webMapping).synthesize();
|
||||
|
||||
assertThat(synthesizedWebMapping1).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertThat(synthesizedWebMapping2).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(synthesizedWebMapping1);
|
||||
assertSynthesized(synthesizedWebMapping2);
|
||||
assertThat(synthesizedWebMapping1).isEqualTo(synthesizedWebMapping2);
|
||||
|
||||
// Synthesizing an annotation from a different MergedAnnotation results in a different synthesized annotation instance.
|
||||
@@ -1595,14 +1596,11 @@ class MergedAnnotationsTests {
|
||||
testSynthesisWithImplicitAliases(GroovyImplicitAliasesSimpleTestConfigurationClass.class, "groovyScript");
|
||||
}
|
||||
|
||||
private void testSynthesisWithImplicitAliases(Class<?> clazz, String expected)
|
||||
throws Exception {
|
||||
ImplicitAliasesTestConfiguration config = clazz.getAnnotation(
|
||||
ImplicitAliasesTestConfiguration.class);
|
||||
private void testSynthesisWithImplicitAliases(Class<?> clazz, String expected) throws Exception {
|
||||
ImplicitAliasesTestConfiguration config = clazz.getAnnotation(ImplicitAliasesTestConfiguration.class);
|
||||
assertThat(config).isNotNull();
|
||||
ImplicitAliasesTestConfiguration synthesized = MergedAnnotation.from(
|
||||
config).synthesize();
|
||||
assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class);
|
||||
ImplicitAliasesTestConfiguration synthesized = MergedAnnotation.from(config).synthesize();
|
||||
assertSynthesized(synthesized);
|
||||
assertThat(synthesized.value()).isEqualTo(expected);
|
||||
assertThat(synthesized.location1()).isEqualTo(expected);
|
||||
assertThat(synthesized.xmlFile()).isEqualTo(expected);
|
||||
@@ -1630,7 +1628,7 @@ class MergedAnnotationsTests {
|
||||
assertThat(config).isNotNull();
|
||||
ImplicitAliasesWithImpliedAliasNamesOmittedTestConfiguration synthesized =
|
||||
MergedAnnotation.from(config).synthesize();
|
||||
assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(synthesized);
|
||||
assertThat(synthesized.value()).isEqualTo(expected);
|
||||
assertThat(synthesized.location()).isEqualTo(expected);
|
||||
assertThat(synthesized.xmlFile()).isEqualTo(expected);
|
||||
@@ -1642,7 +1640,7 @@ class MergedAnnotationsTests {
|
||||
ImplicitAliasesForAliasPairTestConfigurationClass.class.getAnnotation(
|
||||
ImplicitAliasesForAliasPairTestConfiguration.class);
|
||||
ImplicitAliasesForAliasPairTestConfiguration synthesized = MergedAnnotation.from(config).synthesize();
|
||||
assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(synthesized);
|
||||
assertThat(synthesized.xmlFile()).isEqualTo("test.xml");
|
||||
assertThat(synthesized.groovyScript()).isEqualTo("test.xml");
|
||||
}
|
||||
@@ -1653,7 +1651,7 @@ class MergedAnnotationsTests {
|
||||
TransitiveImplicitAliasesTestConfigurationClass.class.getAnnotation(
|
||||
TransitiveImplicitAliasesTestConfiguration.class);
|
||||
TransitiveImplicitAliasesTestConfiguration synthesized = MergedAnnotation.from(config).synthesize();
|
||||
assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(synthesized);
|
||||
assertThat(synthesized.xml()).isEqualTo("test.xml");
|
||||
assertThat(synthesized.groovy()).isEqualTo("test.xml");
|
||||
}
|
||||
@@ -1665,7 +1663,7 @@ class MergedAnnotationsTests {
|
||||
TransitiveImplicitAliasesForAliasPairTestConfiguration.class);
|
||||
TransitiveImplicitAliasesForAliasPairTestConfiguration synthesized = MergedAnnotation.from(
|
||||
config).synthesize();
|
||||
assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(synthesized);
|
||||
assertThat(synthesized.xml()).isEqualTo("test.xml");
|
||||
assertThat(synthesized.groovy()).isEqualTo("test.xml");
|
||||
}
|
||||
@@ -1724,7 +1722,7 @@ class MergedAnnotationsTests {
|
||||
Map<String, Object> map = Collections.singletonMap("value", "webController");
|
||||
MergedAnnotation<Component> annotation = MergedAnnotation.of(Component.class, map);
|
||||
Component synthesizedComponent = annotation.synthesize();
|
||||
assertThat(synthesizedComponent).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(synthesizedComponent);
|
||||
assertThat(synthesizedComponent.value()).isEqualTo("webController");
|
||||
}
|
||||
|
||||
@@ -1745,7 +1743,7 @@ class MergedAnnotationsTests {
|
||||
MergedAnnotation<ComponentScanSingleFilter> annotation = MergedAnnotation.of(
|
||||
ComponentScanSingleFilter.class, map);
|
||||
ComponentScanSingleFilter synthesizedComponentScan = annotation.synthesize();
|
||||
assertThat(synthesizedComponentScan).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(synthesizedComponentScan);
|
||||
assertThat(synthesizedComponentScan.value().pattern()).isEqualTo("newFoo");
|
||||
}
|
||||
|
||||
@@ -1769,7 +1767,7 @@ class MergedAnnotationsTests {
|
||||
MergedAnnotation<ComponentScan> annotation = MergedAnnotation.of(
|
||||
ComponentScan.class, map);
|
||||
ComponentScan synthesizedComponentScan = annotation.synthesize();
|
||||
assertThat(synthesizedComponentScan).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(synthesizedComponentScan);
|
||||
assertThat(Arrays.stream(synthesizedComponentScan.excludeFilters()).map(
|
||||
Filter::pattern)).containsExactly("newFoo", "newBar");
|
||||
}
|
||||
@@ -1888,7 +1886,7 @@ class MergedAnnotationsTests {
|
||||
assertThat(component).isNotNull();
|
||||
Map<String, Object> attributes = MergedAnnotation.from(component).asMap();
|
||||
Component synthesized = MergedAnnotation.of(Component.class, attributes).synthesize();
|
||||
assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(synthesized);
|
||||
assertThat(synthesized).isEqualTo(component);
|
||||
}
|
||||
|
||||
@@ -2047,7 +2045,7 @@ class MergedAnnotationsTests {
|
||||
assertThat(annotation).isNotNull();
|
||||
MergedAnnotation<Annotation> mergedAnnotation = MergedAnnotation.from(annotation);
|
||||
Annotation synthesizedAnnotation = mergedAnnotation.synthesize();
|
||||
assertThat(synthesizedAnnotation).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(synthesizedAnnotation);
|
||||
assertThat(mergedAnnotation.getString("name")).isEqualTo("test");
|
||||
assertThat(mergedAnnotation.getString("path")).isEqualTo("/test");
|
||||
assertThat(mergedAnnotation.getString("value")).isEqualTo("/test");
|
||||
@@ -2058,10 +2056,10 @@ class MergedAnnotationsTests {
|
||||
Hierarchy hierarchy = HierarchyClass.class.getAnnotation(Hierarchy.class);
|
||||
assertThat(hierarchy).isNotNull();
|
||||
Hierarchy synthesizedHierarchy = MergedAnnotation.from(hierarchy).synthesize();
|
||||
assertThat(synthesizedHierarchy).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(synthesizedHierarchy);
|
||||
TestConfiguration[] configs = synthesizedHierarchy.value();
|
||||
assertThat(configs).isNotNull();
|
||||
assertThat(configs).allMatch(SynthesizedAnnotation.class::isInstance);
|
||||
assertThat(configs).allMatch(AnnotationUtils::isSynthesizedAnnotation);
|
||||
assertThat(configs).extracting(TestConfiguration::value).containsExactly("A", "B");
|
||||
assertThat(configs).extracting(TestConfiguration::location).containsExactly("A", "B");
|
||||
|
||||
@@ -2082,7 +2080,7 @@ class MergedAnnotationsTests {
|
||||
assertThat(charsContainer).isNotNull();
|
||||
CharsContainer synthesizedCharsContainer = MergedAnnotation.from(
|
||||
charsContainer).synthesize();
|
||||
assertThat(synthesizedCharsContainer).isInstanceOf(SynthesizedAnnotation.class);
|
||||
assertSynthesized(synthesizedCharsContainer);
|
||||
char[] chars = synthesizedCharsContainer.chars();
|
||||
assertThat(chars).containsExactly('x', 'y', 'z');
|
||||
// Alter array returned from synthesized annotation
|
||||
@@ -3682,4 +3680,12 @@ class MergedAnnotationsTests {
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
static void assertSynthesized(Annotation annotation) {
|
||||
assertThat(AnnotationUtils.isSynthesizedAnnotation(annotation)).as("synthesized annotation").isTrue();
|
||||
}
|
||||
|
||||
static void assertNotSynthesized(Annotation annotation) {
|
||||
assertThat(AnnotationUtils.isSynthesizedAnnotation(annotation)).as("synthesized annotation").isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* 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.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Repeatable;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for various ways to search for repeatable annotations that are
|
||||
* nested (i.e., repeatable annotations used as meta-annotations on other
|
||||
* repeatable annotations).
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.3.24
|
||||
* @see https://github.com/spring-projects/spring-framework/issues/20279
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
class NestedRepeatableAnnotationsTests {
|
||||
|
||||
@Nested
|
||||
class SingleRepeatableAnnotationTests {
|
||||
|
||||
private final Method method = ReflectionUtils.findMethod(getClass(), "annotatedMethod");
|
||||
|
||||
@Test
|
||||
void streamRepeatableAnnotations_MergedAnnotationsApi() {
|
||||
Set<A> annotations = MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY)
|
||||
.stream(A.class).collect(MergedAnnotationCollectors.toAnnotationSet());
|
||||
// Merged, so we expect to find @A once with its value coming from @B(5).
|
||||
assertThat(annotations).extracting(A::value).containsExactly(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findMergedRepeatableAnnotations_AnnotatedElementUtils() {
|
||||
Set<A> annotations = AnnotatedElementUtils.findMergedRepeatableAnnotations(method, A.class);
|
||||
// Merged, so we expect to find @A once with its value coming from @B(5).
|
||||
assertThat(annotations).extracting(A::value).containsExactly(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMergedRepeatableAnnotationsWithStandardRepeatables_AnnotatedElementUtils() {
|
||||
Set<A> annotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, A.class);
|
||||
// Merged, so we expect to find @A once with its value coming from @B(5).
|
||||
assertThat(annotations).extracting(A::value).containsExactly(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMergedRepeatableAnnotationsWithExplicitContainer_AnnotatedElementUtils() {
|
||||
Set<A> annotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, A.class, A.Container.class);
|
||||
// Merged, so we expect to find @A once with its value coming from @B(5).
|
||||
assertThat(annotations).extracting(A::value).containsExactly(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void getRepeatableAnnotations_AnnotationUtils() {
|
||||
Set<A> annotations = AnnotationUtils.getRepeatableAnnotations(method, A.class);
|
||||
// Not merged, so we expect to find @A once with the default value of 0.
|
||||
// @A will actually be found twice, but we have Set semantics here.
|
||||
assertThat(annotations).extracting(A::value).containsExactly(0);
|
||||
}
|
||||
|
||||
@B(5)
|
||||
void annotatedMethod() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class MultipleRepeatableAnnotationsTests {
|
||||
|
||||
private final Method method = ReflectionUtils.findMethod(getClass(), "annotatedMethod");
|
||||
|
||||
@Test
|
||||
void streamRepeatableAnnotationsWithStandardRepeatables_MergedAnnotationsApi() {
|
||||
RepeatableContainers repeatableContainers = RepeatableContainers.standardRepeatables();
|
||||
Set<A> annotations = MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY, repeatableContainers)
|
||||
.stream(A.class).collect(MergedAnnotationCollectors.toAnnotationSet());
|
||||
// Merged, so we expect to find @A twice with values coming from @B(5) and @B(10).
|
||||
assertThat(annotations).extracting(A::value).containsExactly(5, 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamRepeatableAnnotationsWithExplicitRepeatables_MergedAnnotationsApi() {
|
||||
RepeatableContainers repeatableContainers =
|
||||
RepeatableContainers.of(A.class, A.Container.class).and(B.Container.class, B.class);
|
||||
Set<A> annotations = MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY, repeatableContainers)
|
||||
.stream(A.class).collect(MergedAnnotationCollectors.toAnnotationSet());
|
||||
// Merged, so we expect to find @A twice with values coming from @B(5) and @B(10).
|
||||
assertThat(annotations).extracting(A::value).containsExactly(5, 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findMergedRepeatableAnnotationsWithStandardRepeatables_AnnotatedElementUtils() {
|
||||
Set<A> annotations = AnnotatedElementUtils.findMergedRepeatableAnnotations(method, A.class);
|
||||
// Merged, so we expect to find @A twice with values coming from @B(5) and @B(10).
|
||||
assertThat(annotations).extracting(A::value).containsExactly(5, 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findMergedRepeatableAnnotationsWithExplicitContainer_AnnotatedElementUtils() {
|
||||
Set<A> annotations = AnnotatedElementUtils.findMergedRepeatableAnnotations(method, A.class, A.Container.class);
|
||||
// When findMergedRepeatableAnnotations(...) is invoked with an explicit container
|
||||
// type, it uses RepeatableContainers.of(...) which limits the repeatable annotation
|
||||
// support to a single container type.
|
||||
//
|
||||
// In this test case, we are therefore limiting the support to @A.Container, which
|
||||
// means that @B.Container is unsupported and effectively ignored as a repeatable
|
||||
// container type.
|
||||
//
|
||||
// Long story, short: the search doesn't find anything.
|
||||
assertThat(annotations).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMergedRepeatableAnnotationsWithStandardRepeatables_AnnotatedElementUtils() {
|
||||
Set<A> annotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, A.class);
|
||||
// Merged, so we expect to find @A twice with values coming from @B(5) and @B(10).
|
||||
assertThat(annotations).extracting(A::value).containsExactly(5, 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMergedRepeatableAnnotationsWithExplicitContainer_AnnotatedElementUtils() {
|
||||
Set<A> annotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, A.class, A.Container.class);
|
||||
// When getMergedRepeatableAnnotations(...) is invoked with an explicit container
|
||||
// type, it uses RepeatableContainers.of(...) which limits the repeatable annotation
|
||||
// support to a single container type.
|
||||
//
|
||||
// In this test case, we are therefore limiting the support to @A.Container, which
|
||||
// means that @B.Container is unsupported and effectively ignored as a repeatable
|
||||
// container type.
|
||||
//
|
||||
// Long story, short: the search doesn't find anything.
|
||||
assertThat(annotations).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void getRepeatableAnnotations_AnnotationUtils() {
|
||||
Set<A> annotations = AnnotationUtils.getRepeatableAnnotations(method, A.class);
|
||||
// Not merged, so we expect to find a single @A with default value of 0.
|
||||
// @A will actually be found twice, but we have Set semantics here.
|
||||
assertThat(annotations).extracting(A::value).containsExactly(0);
|
||||
}
|
||||
|
||||
@B(5)
|
||||
@B(10)
|
||||
void annotatedMethod() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@Repeatable(A.Container.class)
|
||||
@interface A {
|
||||
|
||||
int value() default 0;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@interface Container {
|
||||
A[] value();
|
||||
}
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@Repeatable(B.Container.class)
|
||||
@A
|
||||
@A
|
||||
@interface B {
|
||||
|
||||
@AliasFor(annotation = A.class)
|
||||
int value();
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@interface Container {
|
||||
B[] value();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -63,7 +63,7 @@ class ResourceRegionEncoderTests extends AbstractLeakCheckingTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncodeResourceRegionFileResource() throws Exception {
|
||||
void shouldEncodeResourceRegionFileResource() {
|
||||
ResourceRegion region = new ResourceRegion(
|
||||
new ClassPathResource("ResourceRegionEncoderTests.txt", getClass()), 0, 6);
|
||||
Flux<DataBuffer> result = this.encoder.encode(Mono.just(region), this.bufferFactory,
|
||||
|
||||
@@ -23,20 +23,32 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashSet;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import okhttp3.mockwebserver.Dispatcher;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Named;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
/**
|
||||
* Unit tests for various {@link Resource} implementations.
|
||||
@@ -44,137 +56,25 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @author Sam Brannen
|
||||
* @since 09.09.2004
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class ResourceTests {
|
||||
|
||||
@Test
|
||||
void byteArrayResource() throws IOException {
|
||||
Resource resource = new ByteArrayResource("testString".getBytes());
|
||||
assertThat(resource.exists()).isTrue();
|
||||
assertThat(resource.isOpen()).isFalse();
|
||||
String content = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
|
||||
assertThat(content).isEqualTo("testString");
|
||||
assertThat(new ByteArrayResource("testString".getBytes())).isEqualTo(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void byteArrayResourceWithDescription() throws IOException {
|
||||
Resource resource = new ByteArrayResource("testString".getBytes(), "my description");
|
||||
assertThat(resource.exists()).isTrue();
|
||||
assertThat(resource.isOpen()).isFalse();
|
||||
String content = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
|
||||
assertThat(content).isEqualTo("testString");
|
||||
assertThat(resource.getDescription().contains("my description")).isTrue();
|
||||
assertThat(new ByteArrayResource("testString".getBytes())).isEqualTo(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamResource() throws IOException {
|
||||
InputStream is = new ByteArrayInputStream("testString".getBytes());
|
||||
Resource resource = new InputStreamResource(is);
|
||||
assertThat(resource.exists()).isTrue();
|
||||
assertThat(resource.isOpen()).isTrue();
|
||||
String content = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
|
||||
assertThat(content).isEqualTo("testString");
|
||||
assertThat(new InputStreamResource(is)).isEqualTo(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamResourceWithDescription() throws IOException {
|
||||
InputStream is = new ByteArrayInputStream("testString".getBytes());
|
||||
Resource resource = new InputStreamResource(is, "my description");
|
||||
assertThat(resource.exists()).isTrue();
|
||||
assertThat(resource.isOpen()).isTrue();
|
||||
String content = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
|
||||
assertThat(content).isEqualTo("testString");
|
||||
assertThat(resource.getDescription().contains("my description")).isTrue();
|
||||
assertThat(new InputStreamResource(is)).isEqualTo(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void classPathResource() throws IOException {
|
||||
Resource resource = new ClassPathResource("org/springframework/core/io/Resource.class");
|
||||
doTestResource(resource);
|
||||
Resource resource2 = new ClassPathResource("org/springframework/core/../core/io/./Resource.class");
|
||||
assertThat(resource2).isEqualTo(resource);
|
||||
Resource resource3 = new ClassPathResource("org/springframework/core/").createRelative("../core/io/./Resource.class");
|
||||
assertThat(resource3).isEqualTo(resource);
|
||||
|
||||
// Check whether equal/hashCode works in a HashSet.
|
||||
HashSet<Resource> resources = new HashSet<>();
|
||||
resources.add(resource);
|
||||
resources.add(resource2);
|
||||
assertThat(resources.size()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void classPathResourceWithClassLoader() throws IOException {
|
||||
Resource resource =
|
||||
new ClassPathResource("org/springframework/core/io/Resource.class", getClass().getClassLoader());
|
||||
doTestResource(resource);
|
||||
assertThat(new ClassPathResource("org/springframework/core/../core/io/./Resource.class", getClass().getClassLoader())).isEqualTo(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void classPathResourceWithClass() throws IOException {
|
||||
Resource resource = new ClassPathResource("Resource.class", getClass());
|
||||
doTestResource(resource);
|
||||
assertThat(new ClassPathResource("Resource.class", getClass())).isEqualTo(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fileSystemResource() throws IOException {
|
||||
String file = getClass().getResource("Resource.class").getFile();
|
||||
Resource resource = new FileSystemResource(file);
|
||||
doTestResource(resource);
|
||||
assertThat(resource).isEqualTo(new FileSystemResource(file));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fileSystemResourceWithFile() throws IOException {
|
||||
File file = new File(getClass().getResource("Resource.class").getFile());
|
||||
Resource resource = new FileSystemResource(file);
|
||||
doTestResource(resource);
|
||||
assertThat(resource).isEqualTo(new FileSystemResource(file));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fileSystemResourceWithFilePath() throws Exception {
|
||||
Path filePath = Paths.get(getClass().getResource("Resource.class").toURI());
|
||||
Resource resource = new FileSystemResource(filePath);
|
||||
doTestResource(resource);
|
||||
assertThat(resource).isEqualTo(new FileSystemResource(filePath));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fileSystemResourceWithPlainPath() {
|
||||
Resource resource = new FileSystemResource("core/io/Resource.class");
|
||||
assertThat(new FileSystemResource("core/../core/io/./Resource.class")).isEqualTo(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void urlResource() throws IOException {
|
||||
Resource resource = new UrlResource(getClass().getResource("Resource.class"));
|
||||
doTestResource(resource);
|
||||
assertThat(resource).isEqualTo(new UrlResource(getClass().getResource("Resource.class")));
|
||||
|
||||
Resource resource2 = new UrlResource("file:core/io/Resource.class");
|
||||
assertThat(new UrlResource("file:core/../core/io/./Resource.class")).isEqualTo(resource2);
|
||||
|
||||
assertThat(new UrlResource("file:/dir/test.txt?argh").getFilename()).isEqualTo("test.txt");
|
||||
assertThat(new UrlResource("file:\\dir\\test.txt?argh").getFilename()).isEqualTo("test.txt");
|
||||
assertThat(new UrlResource("file:\\dir/test.txt?argh").getFilename()).isEqualTo("test.txt");
|
||||
}
|
||||
|
||||
private void doTestResource(Resource resource) throws IOException {
|
||||
@ParameterizedTest(name = "{index}: {0}")
|
||||
@MethodSource("resource")
|
||||
void resourceIsValid(Resource resource) throws Exception {
|
||||
assertThat(resource.getFilename()).isEqualTo("Resource.class");
|
||||
assertThat(resource.getURL().getFile().endsWith("Resource.class")).isTrue();
|
||||
assertThat(resource.exists()).isTrue();
|
||||
assertThat(resource.isReadable()).isTrue();
|
||||
assertThat(resource.contentLength() > 0).isTrue();
|
||||
assertThat(resource.lastModified() > 0).isTrue();
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{index}: {0}")
|
||||
@MethodSource("resource")
|
||||
void resourceCreateRelative(Resource resource) throws Exception {
|
||||
Resource relative1 = resource.createRelative("ClassPathResource.class");
|
||||
assertThat(relative1.getFilename()).isEqualTo("ClassPathResource.class");
|
||||
assertThat(relative1.getURL().getFile().endsWith("ClassPathResource.class")).isTrue();
|
||||
@@ -182,7 +82,11 @@ class ResourceTests {
|
||||
assertThat(relative1.isReadable()).isTrue();
|
||||
assertThat(relative1.contentLength() > 0).isTrue();
|
||||
assertThat(relative1.lastModified() > 0).isTrue();
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{index}: {0}")
|
||||
@MethodSource("resource")
|
||||
void resourceCreateRelativeWithFolder(Resource resource) throws Exception {
|
||||
Resource relative2 = resource.createRelative("support/ResourcePatternResolver.class");
|
||||
assertThat(relative2.getFilename()).isEqualTo("ResourcePatternResolver.class");
|
||||
assertThat(relative2.getURL().getFile().endsWith("ResourcePatternResolver.class")).isTrue();
|
||||
@@ -190,7 +94,11 @@ class ResourceTests {
|
||||
assertThat(relative2.isReadable()).isTrue();
|
||||
assertThat(relative2.contentLength() > 0).isTrue();
|
||||
assertThat(relative2.lastModified() > 0).isTrue();
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{index}: {0}")
|
||||
@MethodSource("resource")
|
||||
void resourceCreateRelativeWithDotPath(Resource resource) throws Exception {
|
||||
Resource relative3 = resource.createRelative("../SpringVersion.class");
|
||||
assertThat(relative3.getFilename()).isEqualTo("SpringVersion.class");
|
||||
assertThat(relative3.getURL().getFile().endsWith("SpringVersion.class")).isTrue();
|
||||
@@ -198,7 +106,11 @@ class ResourceTests {
|
||||
assertThat(relative3.isReadable()).isTrue();
|
||||
assertThat(relative3.contentLength() > 0).isTrue();
|
||||
assertThat(relative3.lastModified() > 0).isTrue();
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{index}: {0}")
|
||||
@MethodSource("resource")
|
||||
void resourceCreateRelativeUnknown(Resource resource) throws Exception {
|
||||
Resource relative4 = resource.createRelative("X.class");
|
||||
assertThat(relative4.exists()).isFalse();
|
||||
assertThat(relative4.isReadable()).isFalse();
|
||||
@@ -208,126 +120,323 @@ class ResourceTests {
|
||||
relative4::lastModified);
|
||||
}
|
||||
|
||||
@Test
|
||||
void classPathResourceWithRelativePath() throws IOException {
|
||||
Resource resource = new ClassPathResource("dir/");
|
||||
Resource relative = resource.createRelative("subdir");
|
||||
assertThat(relative).isEqualTo(new ClassPathResource("dir/subdir"));
|
||||
@ParameterizedTest(name = "{index}: {0}")
|
||||
@MethodSource("resource")
|
||||
void loadingMissingResourceFails(Resource resource) {
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
|
||||
resource.createRelative("X").getInputStream());
|
||||
}
|
||||
|
||||
@Test
|
||||
void fileSystemResourceWithRelativePath() throws IOException {
|
||||
Resource resource = new FileSystemResource("dir/");
|
||||
Resource relative = resource.createRelative("subdir");
|
||||
assertThat(relative).isEqualTo(new FileSystemResource("dir/subdir"));
|
||||
@ParameterizedTest(name = "{index}: {0}")
|
||||
@MethodSource("resource")
|
||||
void readingMissingResourceFails(Resource resource) {
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
|
||||
resource.createRelative("X").readableChannel());
|
||||
}
|
||||
|
||||
@Test
|
||||
void urlResourceWithRelativePath() throws IOException {
|
||||
Resource resource = new UrlResource("file:dir/");
|
||||
Resource relative = resource.createRelative("subdir");
|
||||
assertThat(relative).isEqualTo(new UrlResource("file:dir/subdir"));
|
||||
private static Stream<Arguments> resource() throws URISyntaxException {
|
||||
URL resourceClass = ResourceTests.class.getResource("Resource.class");
|
||||
Path resourceClassFilePath = Paths.get(resourceClass.toURI());
|
||||
return Stream.of(
|
||||
Arguments.of(Named.of("ClassPathResource", new ClassPathResource("org/springframework/core/io/Resource.class"))),
|
||||
Arguments.of(Named.of("ClassPathResource with ClassLoader", new ClassPathResource("org/springframework/core/io/Resource.class", ResourceTests.class.getClassLoader()))),
|
||||
Arguments.of(Named.of("ClassPathResource with Class", new ClassPathResource("Resource.class", ResourceTests.class))),
|
||||
Arguments.of(Named.of("FileSystemResource", new FileSystemResource(resourceClass.getFile()))),
|
||||
Arguments.of(Named.of("FileSystemResource with File", new FileSystemResource(new File(resourceClass.getFile())))),
|
||||
Arguments.of(Named.of("FileSystemResource with File path", new FileSystemResource(resourceClassFilePath))),
|
||||
Arguments.of(Named.of("UrlResource", new UrlResource(resourceClass)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonFileResourceExists() throws Exception {
|
||||
URL url = new URL("https://spring.io/");
|
||||
|
||||
// Abort if spring.io is not reachable.
|
||||
assumeTrue(urlIsReachable(url));
|
||||
@Nested
|
||||
class ByteArrayResourceTests {
|
||||
|
||||
Resource resource = new UrlResource(url);
|
||||
assertThat(resource.exists()).isTrue();
|
||||
}
|
||||
|
||||
private boolean urlIsReachable(URL url) {
|
||||
try {
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("HEAD");
|
||||
connection.setReadTimeout(5_000);
|
||||
return connection.getResponseCode() == HttpURLConnection.HTTP_OK;
|
||||
@Test
|
||||
void hasContent() throws Exception {
|
||||
Resource resource = new ByteArrayResource("testString".getBytes());
|
||||
assertThat(resource.exists()).isTrue();
|
||||
assertThat(resource.isOpen()).isFalse();
|
||||
String content = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
|
||||
assertThat(content).isEqualTo("testString");
|
||||
assertThat(new ByteArrayResource("testString".getBytes())).isEqualTo(resource);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return false;
|
||||
|
||||
@Test
|
||||
void isNotOpen() {
|
||||
Resource resource = new ByteArrayResource("testString".getBytes());
|
||||
assertThat(resource.exists()).isTrue();
|
||||
assertThat(resource.isOpen()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasDescription() {
|
||||
Resource resource = new ByteArrayResource("testString".getBytes(), "my description");
|
||||
assertThat(resource.getDescription().contains("my description")).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class InputStreamResourceTests {
|
||||
|
||||
@Test
|
||||
void hasContent() throws Exception {
|
||||
InputStream is = new ByteArrayInputStream("testString".getBytes());
|
||||
Resource resource = new InputStreamResource(is);
|
||||
String content = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
|
||||
assertThat(content).isEqualTo("testString");
|
||||
assertThat(new InputStreamResource(is)).isEqualTo(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isOpen() {
|
||||
InputStream is = new ByteArrayInputStream("testString".getBytes());
|
||||
Resource resource = new InputStreamResource(is);
|
||||
assertThat(resource.exists()).isTrue();
|
||||
assertThat(resource.isOpen()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasDescription() {
|
||||
InputStream is = new ByteArrayInputStream("testString".getBytes());
|
||||
Resource resource = new InputStreamResource(is, "my description");
|
||||
assertThat(resource.getDescription().contains("my description")).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void abstractResourceExceptions() throws Exception {
|
||||
final String name = "test-resource";
|
||||
|
||||
Resource resource = new AbstractResource() {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return name;
|
||||
}
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
};
|
||||
@Nested
|
||||
class ClassPathResourceTests {
|
||||
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(
|
||||
resource::getURL)
|
||||
.withMessageContaining(name);
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(
|
||||
resource::getFile)
|
||||
.withMessageContaining(name);
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
|
||||
resource.createRelative("/testing"))
|
||||
.withMessageContaining(name);
|
||||
@Test
|
||||
void equalsAndHashCode() {
|
||||
Resource resource = new ClassPathResource("org/springframework/core/io/Resource.class");
|
||||
Resource resource2 = new ClassPathResource("org/springframework/core/../core/io/./Resource.class");
|
||||
Resource resource3 = new ClassPathResource("org/springframework/core/").createRelative("../core/io/./Resource.class");
|
||||
assertThat(resource2).isEqualTo(resource);
|
||||
assertThat(resource3).isEqualTo(resource);
|
||||
// Check whether equal/hashCode works in a HashSet.
|
||||
HashSet<Resource> resources = new HashSet<>();
|
||||
resources.add(resource);
|
||||
resources.add(resource2);
|
||||
assertThat(resources.size()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resourcesWithDifferentPathsAreEqual() {
|
||||
Resource resource = new ClassPathResource("org/springframework/core/io/Resource.class", getClass().getClassLoader());
|
||||
ClassPathResource sameResource = new ClassPathResource("org/springframework/core/../core/io/./Resource.class", getClass().getClassLoader());
|
||||
assertThat(sameResource).isEqualTo(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void relativeResourcesAreEqual() throws Exception {
|
||||
Resource resource = new ClassPathResource("dir/");
|
||||
Resource relative = resource.createRelative("subdir");
|
||||
assertThat(relative).isEqualTo(new ClassPathResource("dir/subdir"));
|
||||
}
|
||||
|
||||
assertThat(resource.getFilename()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void contentLength() throws IOException {
|
||||
AbstractResource resource = new AbstractResource() {
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(new byte[] { 'a', 'b', 'c' });
|
||||
@Nested
|
||||
class FileSystemResourceTests {
|
||||
|
||||
@Test
|
||||
void sameResourceIsEqual() {
|
||||
String file = getClass().getResource("Resource.class").getFile();
|
||||
Resource resource = new FileSystemResource(file);
|
||||
assertThat(resource).isEqualTo(new FileSystemResource(file));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameResourceFromFileIsEqual() {
|
||||
File file = new File(getClass().getResource("Resource.class").getFile());
|
||||
Resource resource = new FileSystemResource(file);
|
||||
assertThat(resource).isEqualTo(new FileSystemResource(file));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameResourceFromFilePathIsEqual() throws Exception {
|
||||
Path filePath = Paths.get(getClass().getResource("Resource.class").toURI());
|
||||
Resource resource = new FileSystemResource(filePath);
|
||||
assertThat(resource).isEqualTo(new FileSystemResource(filePath));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameResourceFromDotPathIsEqual() {
|
||||
Resource resource = new FileSystemResource("core/io/Resource.class");
|
||||
assertThat(new FileSystemResource("core/../core/io/./Resource.class")).isEqualTo(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void relativeResourcesAreEqual() throws Exception {
|
||||
Resource resource = new FileSystemResource("dir/");
|
||||
Resource relative = resource.createRelative("subdir");
|
||||
assertThat(relative).isEqualTo(new FileSystemResource("dir/subdir"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readableChannelProvidesContent() throws Exception {
|
||||
Resource resource = new FileSystemResource(getClass().getResource("Resource.class").getFile());
|
||||
try (ReadableByteChannel channel = resource.readableChannel()) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate((int) resource.contentLength());
|
||||
channel.read(buffer);
|
||||
buffer.rewind();
|
||||
assertThat(buffer.limit() > 0).isTrue();
|
||||
}
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
assertThat(resource.contentLength()).isEqualTo(3L);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void readableChannel() throws IOException {
|
||||
Resource resource = new FileSystemResource(getClass().getResource("Resource.class").getFile());
|
||||
try (ReadableByteChannel channel = resource.readableChannel()) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate((int) resource.contentLength());
|
||||
channel.read(buffer);
|
||||
buffer.rewind();
|
||||
assertThat(buffer.limit() > 0).isTrue();
|
||||
@Nested
|
||||
class UrlResourceTests {
|
||||
|
||||
private MockWebServer server = new MockWebServer();
|
||||
|
||||
@Test
|
||||
void sameResourceWithRelativePathIsEqual() throws Exception {
|
||||
Resource resource = new UrlResource("file:core/io/Resource.class");
|
||||
assertThat(new UrlResource("file:core/../core/io/./Resource.class")).isEqualTo(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void filenameIsExtractedFromFilePath() throws Exception {
|
||||
assertThat(new UrlResource("file:/dir/test.txt?argh").getFilename()).isEqualTo("test.txt");
|
||||
assertThat(new UrlResource("file:\\dir\\test.txt?argh").getFilename()).isEqualTo("test.txt");
|
||||
assertThat(new UrlResource("file:\\dir/test.txt?argh").getFilename()).isEqualTo("test.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void relativeResourcesAreEqual() throws Exception {
|
||||
Resource resource = new UrlResource("file:dir/");
|
||||
Resource relative = resource.createRelative("subdir");
|
||||
assertThat(relative).isEqualTo(new UrlResource("file:dir/subdir"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingRemoteResourceDoesNotExist() throws Exception {
|
||||
String baseUrl = startServer();
|
||||
UrlResource resource = new UrlResource(baseUrl + "/missing");
|
||||
assertThat(resource.exists()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void remoteResourceExists() throws Exception {
|
||||
String baseUrl = startServer();
|
||||
UrlResource resource = new UrlResource(baseUrl + "/resource");
|
||||
assertThat(resource.exists()).isTrue();
|
||||
assertThat(resource.contentLength()).isEqualTo(6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void canCustomizeHttpUrlConnectionForExists() throws Exception {
|
||||
String baseUrl = startServer();
|
||||
CustomResource resource = new CustomResource(baseUrl + "/resource");
|
||||
assertThat(resource.exists()).isTrue();
|
||||
RecordedRequest request = this.server.takeRequest();
|
||||
assertThat(request.getMethod()).isEqualTo("HEAD");
|
||||
assertThat(request.getHeader("Framework-Name")).isEqualTo("Spring");
|
||||
}
|
||||
|
||||
@Test
|
||||
void canCustomizeHttpUrlConnectionForRead() throws Exception {
|
||||
String baseUrl = startServer();
|
||||
CustomResource resource = new CustomResource(baseUrl + "/resource");
|
||||
assertThat(resource.getInputStream()).hasContent("Spring");
|
||||
RecordedRequest request = this.server.takeRequest();
|
||||
assertThat(request.getMethod()).isEqualTo("GET");
|
||||
assertThat(request.getHeader("Framework-Name")).isEqualTo("Spring");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void shutdown() throws Exception {
|
||||
this.server.shutdown();
|
||||
}
|
||||
|
||||
private String startServer() throws Exception {
|
||||
this.server.setDispatcher(new ResourceDispatcher());
|
||||
this.server.start();
|
||||
return "http://localhost:" + this.server.getPort();
|
||||
}
|
||||
|
||||
class CustomResource extends UrlResource {
|
||||
|
||||
public CustomResource(String path) throws MalformedURLException {
|
||||
super(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void customizeConnection(HttpURLConnection con) throws IOException {
|
||||
con.setRequestProperty("Framework-Name", "Spring");
|
||||
}
|
||||
}
|
||||
|
||||
class ResourceDispatcher extends Dispatcher {
|
||||
|
||||
@Override
|
||||
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
|
||||
if (request.getPath().equals("/resource")) {
|
||||
switch (request.getMethod()) {
|
||||
case "HEAD":
|
||||
return new MockResponse()
|
||||
.addHeader("Content-Length", "6");
|
||||
case "GET":
|
||||
return new MockResponse()
|
||||
.addHeader("Content-Length", "6")
|
||||
.addHeader("Content-Type", "text/plain")
|
||||
.setBody("Spring");
|
||||
}
|
||||
}
|
||||
return new MockResponse().setResponseCode(404);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamNotFoundOnFileSystemResource() throws IOException {
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
|
||||
new FileSystemResource(getClass().getResource("Resource.class").getFile()).createRelative("X").getInputStream());
|
||||
}
|
||||
@Nested
|
||||
class AbstractResourceTests {
|
||||
|
||||
@Test
|
||||
void readableChannelNotFoundOnFileSystemResource() throws IOException {
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
|
||||
new FileSystemResource(getClass().getResource("Resource.class").getFile()).createRelative("X").readableChannel());
|
||||
}
|
||||
@Test
|
||||
void missingResourceIsNotReadable() {
|
||||
final String name = "test-resource";
|
||||
|
||||
@Test
|
||||
void inputStreamNotFoundOnClassPathResource() throws IOException {
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
|
||||
new ClassPathResource("Resource.class", getClass()).createRelative("X").getInputStream());
|
||||
}
|
||||
Resource resource = new AbstractResource() {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
};
|
||||
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(resource::getURL)
|
||||
.withMessageContaining(name);
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(resource::getFile)
|
||||
.withMessageContaining(name);
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
|
||||
resource.createRelative("/testing")).withMessageContaining(name);
|
||||
assertThat(resource.getFilename()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasContentLength() throws Exception {
|
||||
AbstractResource resource = new AbstractResource() {
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(new byte[] {'a', 'b', 'c'});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
assertThat(resource.contentLength()).isEqualTo(3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readableChannelNotFoundOnClassPathResource() throws IOException {
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
|
||||
new ClassPathResource("Resource.class", getClass()).createRelative("X").readableChannel());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+21
@@ -990,6 +990,27 @@ class DataBufferUtilsTests extends AbstractDataBufferAllocatingTests {
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
void propagateContextPath(DataBufferFactory bufferFactory) throws IOException {
|
||||
Path path = Paths.get(this.resource.getURI());
|
||||
Path out = Files.createTempFile("data-buffer-utils-tests", ".tmp");
|
||||
|
||||
Flux<Void> result = DataBufferUtils.read(path, bufferFactory, 1024, StandardOpenOption.READ)
|
||||
.transformDeferredContextual((f, ctx) -> {
|
||||
assertThat(ctx.getOrDefault("key", "EMPTY")).isEqualTo("TEST");
|
||||
return f;
|
||||
})
|
||||
.transform(f -> DataBufferUtils.write(f, out))
|
||||
.transformDeferredContextual((f, ctx) -> {
|
||||
assertThat(ctx.getOrDefault("key", "EMPTY")).isEqualTo("TEST");
|
||||
return f;
|
||||
})
|
||||
.contextWrite(Context.of("key", "TEST"));
|
||||
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
private static class ZeroDemandSubscriber extends BaseSubscriber<DataBuffer> {
|
||||
|
||||
@Override
|
||||
|
||||
+2
-3
@@ -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.
|
||||
@@ -35,8 +35,7 @@ class LeakAwareDataBufferFactoryTests {
|
||||
void leak() {
|
||||
DataBuffer dataBuffer = this.bufferFactory.allocateBuffer();
|
||||
try {
|
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(
|
||||
this.bufferFactory::checkForLeaks);
|
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(this.bufferFactory::checkForLeaks);
|
||||
}
|
||||
finally {
|
||||
release(dataBuffer);
|
||||
|
||||
+242
-101
@@ -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.
|
||||
@@ -16,16 +16,19 @@
|
||||
|
||||
package org.springframework.core.io.support;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -33,8 +36,9 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* If this test case fails, uncomment diagnostics in the
|
||||
* {@link #assertProtocolAndFilenames} method.
|
||||
* Tests for {@link PathMatchingResourcePatternResolver}.
|
||||
*
|
||||
* <p>If tests fail, uncomment the diagnostics in {@link #assertFilenames(String, boolean, String...)}.
|
||||
*
|
||||
* @author Oliver Hutchison
|
||||
* @author Juergen Hoeller
|
||||
@@ -44,124 +48,261 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
*/
|
||||
class PathMatchingResourcePatternResolverTests {
|
||||
|
||||
private static final String[] CLASSES_IN_CORE_IO_SUPPORT =
|
||||
new String[] {"EncodedResource.class", "LocalizedResourceHelper.class",
|
||||
"PathMatchingResourcePatternResolver.class", "PropertiesLoaderSupport.class",
|
||||
"PropertiesLoaderUtils.class", "ResourceArrayPropertyEditor.class",
|
||||
"ResourcePatternResolver.class", "ResourcePatternUtils.class"};
|
||||
private static final String[] CLASSES_IN_CORE_IO_SUPPORT = { "EncodedResource.class",
|
||||
"LocalizedResourceHelper.class", "PathMatchingResourcePatternResolver.class", "PropertiesLoaderSupport.class",
|
||||
"PropertiesLoaderUtils.class", "ResourceArrayPropertyEditor.class", "ResourcePatternResolver.class",
|
||||
"ResourcePatternUtils.class", "SpringFactoriesLoader.class" };
|
||||
|
||||
private static final String[] TEST_CLASSES_IN_CORE_IO_SUPPORT =
|
||||
new String[] {"PathMatchingResourcePatternResolverTests.class"};
|
||||
private static final String[] TEST_CLASSES_IN_CORE_IO_SUPPORT = { "PathMatchingResourcePatternResolverTests.class" };
|
||||
|
||||
private static final String[] CLASSES_IN_REACTOR_UTIL_ANNOTATIONS =
|
||||
new String[] {"NonNull.class", "NonNullApi.class", "Nullable.class"};
|
||||
|
||||
private PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
private static final String[] CLASSES_IN_REACTOR_UTIL_ANNOTATION = { "NonNull.class", "NonNullApi.class", "Nullable.class" };
|
||||
|
||||
|
||||
@Test
|
||||
void invalidPrefixWithPatternElementInIt() throws IOException {
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
|
||||
resolver.getResources("xx**:**/*.xy"));
|
||||
private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
|
||||
|
||||
@Nested
|
||||
class InvalidPatterns {
|
||||
|
||||
@Test
|
||||
void invalidPrefixWithPatternElementInItThrowsException() {
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> resolver.getResources("xx**:**/*.xy"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleResourceOnFileSystem() throws IOException {
|
||||
Resource[] resources =
|
||||
resolver.getResources("org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.class");
|
||||
assertThat(resources.length).isEqualTo(1);
|
||||
assertProtocolAndFilenames(resources, "file", "PathMatchingResourcePatternResolverTests.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleResourceInJar() throws IOException {
|
||||
Resource[] resources = resolver.getResources("org/reactivestreams/Publisher.class");
|
||||
assertThat(resources.length).isEqualTo(1);
|
||||
assertProtocolAndFilenames(resources, "jar", "Publisher.class");
|
||||
}
|
||||
@Nested
|
||||
class FileSystemResources {
|
||||
|
||||
@Disabled
|
||||
@Test
|
||||
void classpathStarWithPatternOnFileSystem() throws IOException {
|
||||
Resource[] resources = resolver.getResources("classpath*:org/springframework/core/io/sup*/*.class");
|
||||
// Have to exclude Clover-generated class files here,
|
||||
// as we might be running as part of a Clover test run.
|
||||
List<Resource> noCloverResources = new ArrayList<>();
|
||||
for (Resource resource : resources) {
|
||||
if (!resource.getFilename().contains("$__CLOVER_")) {
|
||||
noCloverResources.add(resource);
|
||||
@Test
|
||||
void singleResourceOnFileSystem() {
|
||||
String pattern = "org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.class";
|
||||
assertExactFilenames(pattern, "PathMatchingResourcePatternResolverTests.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
void classpathStarWithPatternOnFileSystem() {
|
||||
String pattern = "classpath*:org/springframework/core/io/sup*/*.class";
|
||||
String[] expectedFilenames = StringUtils.concatenateStringArrays(CLASSES_IN_CORE_IO_SUPPORT, TEST_CLASSES_IN_CORE_IO_SUPPORT);
|
||||
assertFilenames(pattern, expectedFilenames);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithHashtagsInTheirFileNames {
|
||||
|
||||
@Test
|
||||
void usingClasspathStarProtocol() {
|
||||
String pattern = "classpath*:org/springframework/core/io/**/resource#test*.txt";
|
||||
String pathPrefix = ".+org/springframework/core/io/";
|
||||
|
||||
assertExactFilenames(pattern, "resource#test1.txt", "resource#test2.txt");
|
||||
assertExactSubPaths(pattern, pathPrefix, "support/resource#test1.txt", "support/resource#test2.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void usingClasspathStarProtocolWithWildcardInPatternAndNotEndingInSlash() throws Exception {
|
||||
String pattern = "classpath*:org/springframework/core/io/sup*";
|
||||
String pathPrefix = ".+org/springframework/core/io/";
|
||||
|
||||
List<String> actualSubPaths = getSubPathsIgnoringClassFiles(pattern, pathPrefix);
|
||||
|
||||
// We DO find "support" if the pattern does NOT end with a slash.
|
||||
assertThat(actualSubPaths).containsExactly("support");
|
||||
}
|
||||
|
||||
@Test
|
||||
void usingFileProtocolWithWildcardInPatternAndNotEndingInSlash() throws Exception {
|
||||
Path testResourcesDir = Paths.get("src/test/resources").toAbsolutePath();
|
||||
String pattern = String.format("file:%s/org/springframework/core/io/sup*", testResourcesDir);
|
||||
String pathPrefix = ".+org/springframework/core/io/";
|
||||
|
||||
List<String> actualSubPaths = getSubPathsIgnoringClassFiles(pattern, pathPrefix);
|
||||
|
||||
// We DO find "support" if the pattern does NOT end with a slash.
|
||||
assertThat(actualSubPaths).containsExactly("support");
|
||||
}
|
||||
|
||||
@Test
|
||||
void usingClasspathStarProtocolWithWildcardInPatternAndEndingInSlash() throws Exception {
|
||||
String pattern = "classpath*:org/springframework/core/io/sup*/";
|
||||
String pathPrefix = ".+org/springframework/core/io/";
|
||||
|
||||
List<String> actualSubPaths = getSubPathsIgnoringClassFiles(pattern, pathPrefix);
|
||||
|
||||
// We do NOT find "support" if the pattern ENDS with a slash.
|
||||
assertThat(actualSubPaths).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void usingFileProtocolWithWildcardInPatternAndEndingInSlash() throws Exception {
|
||||
Path testResourcesDir = Paths.get("src/test/resources").toAbsolutePath();
|
||||
String pattern = String.format("file:%s/org/springframework/core/io/sup*/", testResourcesDir);
|
||||
String pathPrefix = ".+org/springframework/core/io/";
|
||||
|
||||
List<String> actualSubPaths = getSubPathsIgnoringClassFiles(pattern, pathPrefix);
|
||||
|
||||
// We do NOT find "support" if the pattern ENDS with a slash.
|
||||
assertThat(actualSubPaths).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void usingClasspathStarProtocolWithWildcardInPatternAndEndingWithSuffixPattern() throws Exception {
|
||||
String pattern = "classpath*:org/springframework/core/io/sup*/*.txt";
|
||||
String pathPrefix = ".+org/springframework/core/io/";
|
||||
|
||||
List<String> actualSubPaths = getSubPathsIgnoringClassFiles(pattern, pathPrefix);
|
||||
|
||||
assertThat(actualSubPaths)
|
||||
.containsExactlyInAnyOrder("support/resource#test1.txt", "support/resource#test2.txt");
|
||||
}
|
||||
|
||||
private List<String> getSubPathsIgnoringClassFiles(String pattern, String pathPrefix) throws IOException {
|
||||
return Arrays.stream(resolver.getResources(pattern))
|
||||
.map(resource -> getPath(resource).replaceFirst(pathPrefix, ""))
|
||||
.filter(name -> !name.endsWith(".class"))
|
||||
.distinct()
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void usingFileProtocolWithoutWildcardInPatternAndEndingInSlashStarStar() {
|
||||
Path testResourcesDir = Paths.get("src/test/resources").toAbsolutePath();
|
||||
String pattern = String.format("file:%s/scanned-resources/**", testResourcesDir);
|
||||
String pathPrefix = ".+?resources/";
|
||||
|
||||
// We do NOT find "scanned-resources" if the pattern ENDS with "/**" AND does NOT otherwise contain a wildcard.
|
||||
assertExactFilenames(pattern, "resource#test1.txt", "resource#test2.txt");
|
||||
assertExactSubPaths(pattern, pathPrefix, "scanned-resources/resource#test1.txt",
|
||||
"scanned-resources/resource#test2.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void usingFileProtocolWithWildcardInPatternAndEndingInSlashStarStar() {
|
||||
Path testResourcesDir = Paths.get("src/test/resources").toAbsolutePath();
|
||||
String pattern = String.format("file:%s/scanned*resources/**", testResourcesDir);
|
||||
String pathPrefix = ".+?resources/";
|
||||
|
||||
// We DO find "scanned-resources" if the pattern ENDS with "/**" AND DOES otherwise contain a wildcard.
|
||||
assertExactFilenames(pattern, "scanned-resources", "resource#test1.txt", "resource#test2.txt");
|
||||
assertExactSubPaths(pattern, pathPrefix, "scanned-resources", "scanned-resources/resource#test1.txt",
|
||||
"scanned-resources/resource#test2.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void usingFileProtocolAndAssertingUrlAndUriSyntax() throws Exception {
|
||||
Path testResourcesDir = Paths.get("src/test/resources").toAbsolutePath();
|
||||
String pattern = String.format("file:%s/scanned-resources/**/resource#test1.txt", testResourcesDir);
|
||||
Resource[] resources = resolver.getResources(pattern);
|
||||
assertThat(resources).hasSize(1);
|
||||
Resource resource = resources[0];
|
||||
assertThat(resource.getFilename()).isEqualTo("resource#test1.txt");
|
||||
// The following assertions serve as regression tests for the lack of the
|
||||
// "authority component" (//) in the returned URI/URL. For example, we are
|
||||
// expecting file:/my/path (or file:/C:/My/Path) instead of file:///my/path.
|
||||
assertThat(resource.getURL().toString()).matches("^file:\\/[^\\/].+test1\\.txt$");
|
||||
assertThat(resource.getURI().toString()).matches("^file:\\/[^\\/].+test1\\.txt$");
|
||||
}
|
||||
}
|
||||
resources = noCloverResources.toArray(new Resource[0]);
|
||||
assertProtocolAndFilenames(resources, "file",
|
||||
StringUtils.concatenateStringArrays(CLASSES_IN_CORE_IO_SUPPORT, TEST_CLASSES_IN_CORE_IO_SUPPORT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourcesOnFileSystemContainingHashtagsInTheirFileNames() throws IOException {
|
||||
Resource[] resources = resolver.getResources("classpath*:org/springframework/core/io/**/resource#test*.txt");
|
||||
assertThat(resources).extracting(Resource::getFile).extracting(File::getName)
|
||||
.containsExactlyInAnyOrder("resource#test1.txt", "resource#test2.txt");
|
||||
|
||||
@Nested
|
||||
class JarResources {
|
||||
|
||||
@Test
|
||||
void singleResourceInJar() {
|
||||
String pattern = "org/reactivestreams/Publisher.class";
|
||||
assertExactFilenames(pattern, "Publisher.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleResourceInRootOfJar() {
|
||||
String pattern = "aspectj_1_5_0.dtd";
|
||||
assertExactFilenames(pattern, "aspectj_1_5_0.dtd");
|
||||
}
|
||||
|
||||
@Test
|
||||
void classpathWithPatternInJar() {
|
||||
String pattern = "classpath:reactor/util/annotation/*.class";
|
||||
assertExactFilenames(pattern, CLASSES_IN_REACTOR_UTIL_ANNOTATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void classpathStarWithPatternInJar() {
|
||||
String pattern = "classpath*:reactor/util/annotation/*.class";
|
||||
assertExactFilenames(pattern, CLASSES_IN_REACTOR_UTIL_ANNOTATION);
|
||||
}
|
||||
|
||||
// Fails in a native image -- https://github.com/oracle/graal/issues/5020
|
||||
@Test
|
||||
void rootPatternRetrievalInJarFiles() throws IOException {
|
||||
assertThat(resolver.getResources("classpath*:aspectj*.dtd")).extracting(Resource::getFilename)
|
||||
.as("Could not find aspectj_1_5_0.dtd in the root of the aspectjweaver jar")
|
||||
.containsExactly("aspectj_1_5_0.dtd");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void classpathWithPatternInJar() throws IOException {
|
||||
Resource[] resources = resolver.getResources("classpath:reactor/util/annotation/*.class");
|
||||
assertProtocolAndFilenames(resources, "jar", CLASSES_IN_REACTOR_UTIL_ANNOTATIONS);
|
||||
|
||||
private void assertFilenames(String pattern, String... filenames) {
|
||||
assertFilenames(pattern, false, filenames);
|
||||
}
|
||||
|
||||
@Test
|
||||
void classpathStarWithPatternInJar() throws IOException {
|
||||
Resource[] resources = resolver.getResources("classpath*:reactor/util/annotation/*.class");
|
||||
assertProtocolAndFilenames(resources, "jar", CLASSES_IN_REACTOR_UTIL_ANNOTATIONS);
|
||||
private void assertExactFilenames(String pattern, String... filenames) {
|
||||
assertFilenames(pattern, true, filenames);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rootPatternRetrievalInJarFiles() throws IOException {
|
||||
Resource[] resources = resolver.getResources("classpath*:*.dtd");
|
||||
boolean found = false;
|
||||
for (Resource resource : resources) {
|
||||
if (resource.getFilename().equals("aspectj_1_5_0.dtd")) {
|
||||
found = true;
|
||||
break;
|
||||
private void assertFilenames(String pattern, boolean exactly, String... filenames) {
|
||||
try {
|
||||
Resource[] resources = resolver.getResources(pattern);
|
||||
List<String> actualNames = Arrays.stream(resources)
|
||||
.map(Resource::getFilename)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Uncomment the following if you encounter problems with matching against the file system.
|
||||
// List<String> expectedNames = Arrays.stream(filenames).sorted().toList();
|
||||
// System.out.println("----------------------------------------------------------------------");
|
||||
// System.out.println("Expected: " + expectedNames);
|
||||
// System.out.println("Actual: " + actualNames);
|
||||
// Arrays.stream(resources).forEach(System.out::println);
|
||||
|
||||
if (exactly) {
|
||||
assertThat(actualNames).as("subset of files found").containsExactlyInAnyOrder(filenames);
|
||||
}
|
||||
else {
|
||||
assertThat(actualNames).as("subset of files found").contains(filenames);
|
||||
}
|
||||
}
|
||||
assertThat(found).as("Could not find aspectj_1_5_0.dtd in the root of the aspectjweaver jar").isTrue();
|
||||
}
|
||||
|
||||
|
||||
private void assertProtocolAndFilenames(Resource[] resources, String protocol, String... filenames)
|
||||
throws IOException {
|
||||
|
||||
// Uncomment the following if you encounter problems with matching against the file system
|
||||
// It shows file locations.
|
||||
// String[] actualNames = new String[resources.length];
|
||||
// for (int i = 0; i < resources.length; i++) {
|
||||
// actualNames[i] = resources[i].getFilename();
|
||||
// }
|
||||
// List sortedActualNames = new LinkedList(Arrays.asList(actualNames));
|
||||
// List expectedNames = new LinkedList(Arrays.asList(fileNames));
|
||||
// Collections.sort(sortedActualNames);
|
||||
// Collections.sort(expectedNames);
|
||||
//
|
||||
// System.out.println("-----------");
|
||||
// System.out.println("Expected: " + StringUtils.collectionToCommaDelimitedString(expectedNames));
|
||||
// System.out.println("Actual: " + StringUtils.collectionToCommaDelimitedString(sortedActualNames));
|
||||
// for (int i = 0; i < resources.length; i++) {
|
||||
// System.out.println(resources[i]);
|
||||
// }
|
||||
|
||||
assertThat(resources.length).as("Correct number of files found").isEqualTo(filenames.length);
|
||||
for (Resource resource : resources) {
|
||||
String actualProtocol = resource.getURL().getProtocol();
|
||||
assertThat(actualProtocol).isEqualTo(protocol);
|
||||
assertFilenameIn(resource, filenames);
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertFilenameIn(Resource resource, String... filenames) {
|
||||
String filename = resource.getFilename();
|
||||
assertThat(Arrays.stream(filenames).anyMatch(filename::endsWith)).as(resource + " does not have a filename that matches any of the specified names").isTrue();
|
||||
private void assertExactSubPaths(String pattern, String pathPrefix, String... subPaths) {
|
||||
try {
|
||||
Resource[] resources = resolver.getResources(pattern);
|
||||
List<String> actualSubPaths = Arrays.stream(resources)
|
||||
.map(resource -> getPath(resource).replaceFirst(pathPrefix, ""))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
assertThat(actualSubPaths).containsExactlyInAnyOrder(subPaths);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String getPath(Resource resource) {
|
||||
// Tests fail if we use resouce.getURL().getPath(). They would also fail on Mac OS when
|
||||
// using resouce.getURI().getPath() if the resource paths are not Unicode normalized.
|
||||
//
|
||||
// On the JVM, all tests should pass when using resouce.getFile().getPath(); however,
|
||||
// we use FileSystemResource#getPath since this test class is sometimes run within a
|
||||
// GraalVM native image which cannot support Path#toFile.
|
||||
//
|
||||
// See: https://github.com/spring-projects/spring-framework/issues/29243
|
||||
return ((FileSystemResource) resource).getPath();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,14 +50,14 @@ class SocketUtilsTests {
|
||||
|
||||
@Test
|
||||
void findAvailableTcpPortWithZeroMinPort() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
org.springframework.util.SocketUtils.findAvailableTcpPort(0));
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> org.springframework.util.SocketUtils.findAvailableTcpPort(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAvailableTcpPortWithNegativeMinPort() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
org.springframework.util.SocketUtils.findAvailableTcpPort(-500));
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> org.springframework.util.SocketUtils.findAvailableTcpPort(-500));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -80,8 +80,8 @@ class SocketUtilsTests {
|
||||
try (ServerSocket socket = ServerSocketFactory.getDefault().createServerSocket(port, 1, InetAddress.getByName("localhost"))) {
|
||||
assertThat(socket).isNotNull();
|
||||
// will only look for the exact port
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
org.springframework.util.SocketUtils.findAvailableTcpPort(port, port))
|
||||
assertThatIllegalStateException().isThrownBy(
|
||||
() -> org.springframework.util.SocketUtils.findAvailableTcpPort(port, port))
|
||||
.withMessageStartingWith("Could not find an available TCP port")
|
||||
.withMessageEndingWith("after 1 attempts");
|
||||
}
|
||||
@@ -123,8 +123,7 @@ class SocketUtilsTests {
|
||||
|
||||
@Test
|
||||
void findAvailableTcpPortsWithRequestedNumberGreaterThanSizeOfRange() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
findAvailableTcpPorts(50, 45000, 45010));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> findAvailableTcpPorts(50, 45000, 45010));
|
||||
}
|
||||
|
||||
|
||||
@@ -132,14 +131,14 @@ class SocketUtilsTests {
|
||||
|
||||
@Test
|
||||
void findAvailableUdpPortWithZeroMinPort() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
org.springframework.util.SocketUtils.findAvailableUdpPort(0));
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> org.springframework.util.SocketUtils.findAvailableUdpPort(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAvailableUdpPortWithNegativeMinPort() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
org.springframework.util.SocketUtils.findAvailableUdpPort(-500));
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> org.springframework.util.SocketUtils.findAvailableUdpPort(-500));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -155,8 +154,8 @@ class SocketUtilsTests {
|
||||
try (DatagramSocket socket = new DatagramSocket(port, InetAddress.getByName("localhost"))) {
|
||||
assertThat(socket).isNotNull();
|
||||
// will only look for the exact port
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
org.springframework.util.SocketUtils.findAvailableUdpPort(port, port))
|
||||
assertThatIllegalStateException().isThrownBy(
|
||||
() -> org.springframework.util.SocketUtils.findAvailableUdpPort(port, port))
|
||||
.withMessageStartingWith("Could not find an available UDP port")
|
||||
.withMessageEndingWith("after 1 attempts");
|
||||
}
|
||||
@@ -198,8 +197,7 @@ class SocketUtilsTests {
|
||||
|
||||
@Test
|
||||
void findAvailableUdpPortsWithRequestedNumberGreaterThanSizeOfRange() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
findAvailableUdpPorts(50, 45000, 45010));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> findAvailableUdpPorts(50, 45000, 45010));
|
||||
}
|
||||
|
||||
|
||||
@@ -226,13 +224,13 @@ class SocketUtilsTests {
|
||||
SortedSet<Integer> ports = org.springframework.util.SocketUtils.findAvailableUdpPorts(numRequested, minPort, maxPort);
|
||||
assertAvailablePorts(ports, numRequested, minPort, maxPort);
|
||||
}
|
||||
|
||||
private void assertPortInRange(int port, int minPort, int maxPort) {
|
||||
assertThat(port >= minPort).as("port [" + port + "] >= " + minPort).isTrue();
|
||||
assertThat(port <= maxPort).as("port [" + port + "] <= " + maxPort).isTrue();
|
||||
assertThat(port).as("port").isBetween(minPort, maxPort);
|
||||
}
|
||||
|
||||
private void assertAvailablePorts(SortedSet<Integer> ports, int numRequested, int minPort, int maxPort) {
|
||||
assertThat(ports.size()).as("number of ports requested").isEqualTo(numRequested);
|
||||
assertThat(ports).as("number of ports requested").hasSize(numRequested);
|
||||
for (int port : ports) {
|
||||
assertPortInRange(port, minPort, maxPort);
|
||||
}
|
||||
|
||||
+10
-10
@@ -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.
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.springframework.core
|
||||
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class KotlinBridgeMethodResolverTests {
|
||||
@@ -25,22 +25,22 @@ class KotlinBridgeMethodResolverTests {
|
||||
fun findBridgedMethod() {
|
||||
val unbridged = GenericRepository::class.java.getDeclaredMethod("delete", Int::class.java)
|
||||
val bridged = GenericRepository::class.java.getDeclaredMethod("delete", Any::class.java)
|
||||
Assertions.assertThat(unbridged.isBridge).isFalse
|
||||
Assertions.assertThat(bridged.isBridge).isTrue
|
||||
assertThat(unbridged.isBridge).isFalse
|
||||
assertThat(bridged.isBridge).isTrue
|
||||
|
||||
Assertions.assertThat(BridgeMethodResolver.findBridgedMethod(unbridged)).`as`("Unbridged method not returned directly").isEqualTo(unbridged)
|
||||
Assertions.assertThat(BridgeMethodResolver.findBridgedMethod(bridged)).`as`("Incorrect bridged method returned").isEqualTo(unbridged)
|
||||
assertThat(BridgeMethodResolver.findBridgedMethod(unbridged)).`as`("Unbridged method not returned directly").isEqualTo(unbridged)
|
||||
assertThat(BridgeMethodResolver.findBridgedMethod(bridged)).`as`("Incorrect bridged method returned").isEqualTo(unbridged)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun findBridgedMethodWithArrays() {
|
||||
val unbridged = GenericRepository::class.java.getDeclaredMethod("delete", Array<Int>::class.java)
|
||||
val bridged = GenericRepository::class.java.getDeclaredMethod("delete", Array<Any>::class.java)
|
||||
Assertions.assertThat(unbridged.isBridge).isFalse
|
||||
Assertions.assertThat(bridged.isBridge).isTrue
|
||||
assertThat(unbridged.isBridge).isFalse
|
||||
assertThat(bridged.isBridge).isTrue
|
||||
|
||||
Assertions.assertThat(BridgeMethodResolver.findBridgedMethod(unbridged)).`as`("Unbridged method not returned directly").isEqualTo(unbridged)
|
||||
Assertions.assertThat(BridgeMethodResolver.findBridgedMethod(bridged)).`as`("Incorrect bridged method returned").isEqualTo(unbridged)
|
||||
assertThat(BridgeMethodResolver.findBridgedMethod(unbridged)).`as`("Unbridged method not returned directly").isEqualTo(unbridged)
|
||||
assertThat(BridgeMethodResolver.findBridgedMethod(bridged)).`as`("Incorrect bridged method returned").isEqualTo(unbridged)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
test 1
|
||||
@@ -0,0 +1 @@
|
||||
test 2
|
||||
+3
-1
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.core.testfixture.io.buffer;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
@@ -42,7 +44,7 @@ public abstract class AbstractLeakCheckingTests {
|
||||
*/
|
||||
@AfterEach
|
||||
final void checkForLeaks() {
|
||||
this.bufferFactory.checkForLeaks();
|
||||
this.bufferFactory.checkForLeaks(Duration.ofSeconds(1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
-1
@@ -81,13 +81,21 @@ public class LeakAwareDataBufferFactory implements DataBufferFactory {
|
||||
* method.
|
||||
*/
|
||||
public void checkForLeaks() {
|
||||
checkForLeaks(Duration.ofSeconds(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of {@link #checkForLeaks()} with the option to wait for buffer release.
|
||||
* @param timeout how long to wait for buffers to be released; 0 for no waiting
|
||||
*/
|
||||
public void checkForLeaks(Duration timeout) {
|
||||
this.trackCreated.set(false);
|
||||
Instant start = Instant.now();
|
||||
while (true) {
|
||||
if (this.created.stream().noneMatch(LeakAwareDataBuffer::isAllocated)) {
|
||||
return;
|
||||
}
|
||||
if (Instant.now().isBefore(start.plus(Duration.ofSeconds(5)))) {
|
||||
if (Instant.now().isBefore(start.plus(timeout))) {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@ Syntax
|
||||
- Need to agree on a standard date format for 'default' processing of dates. Currently it is:
|
||||
formatter = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", Locale.UK);
|
||||
// this is something of this format: "Wed, 4 Jul 2001 12:08:56 GMT"
|
||||
// https://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html
|
||||
// https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html
|
||||
- See LiteralTests for Date (4,5,6) - should date take an expression rather than be hardcoded in the grammar
|
||||
to take 2 strings only?
|
||||
- when doing arithmetic, eg. 8.4 / 4 and the user asks for an Integer return type - do we silently coerce or
|
||||
say we cannot as it won't fit into an int? (see OperatorTests.testMathOperatorDivide04)
|
||||
- Is $index within projection/selection useful or just cute?
|
||||
- All reals are represented as Doubles (so 1.25f is held internally as a double, can be converted to float when required though) - is that ok?
|
||||
- All reals are represented as Doubles (so 1.25f is held internally as a double, can be converted to float when required though) - is that ok?
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ package org.springframework.expression;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Super class for exceptions that can occur whilst processing expressions.
|
||||
* Superclass for exceptions that can occur whilst processing expressions.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Phillip Webb
|
||||
|
||||
@@ -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.
|
||||
@@ -32,6 +32,7 @@ import org.springframework.util.ObjectUtils;
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public class Elvis extends SpelNodeImpl {
|
||||
@@ -64,7 +65,7 @@ public class Elvis extends SpelNodeImpl {
|
||||
|
||||
@Override
|
||||
public String toStringAST() {
|
||||
return getChild(0).toStringAST() + " ?: " + getChild(1).toStringAST();
|
||||
return "(" + getChild(0).toStringAST() + " ?: " + getChild(1).toStringAST() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
-2
@@ -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.
|
||||
@@ -32,6 +32,7 @@ import org.springframework.util.ObjectUtils;
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public class Ternary extends SpelNodeImpl {
|
||||
@@ -62,7 +63,7 @@ public class Ternary extends SpelNodeImpl {
|
||||
|
||||
@Override
|
||||
public String toStringAST() {
|
||||
return getChild(0).toStringAST() + " ? " + getChild(1).toStringAST() + " : " + getChild(2).toStringAST();
|
||||
return "(" + getChild(0).toStringAST() + " ? " + getChild(1).toStringAST() + " : " + getChild(2).toStringAST() + ")";
|
||||
}
|
||||
|
||||
private void computeExitTypeDescriptor() {
|
||||
|
||||
+22
@@ -116,6 +116,10 @@ class EvaluationTests extends AbstractExpressionTests {
|
||||
void elvisOperator() {
|
||||
evaluate("'Andy'?:'Dave'", "Andy", String.class);
|
||||
evaluate("null?:'Dave'", "Dave", String.class);
|
||||
evaluate("3?:1", 3, Integer.class);
|
||||
evaluate("(2*3)?:1*10", 6, Integer.class);
|
||||
evaluate("null?:2*10", 20, Integer.class);
|
||||
evaluate("(null?:1)*10", 10, Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -624,6 +628,24 @@ class EvaluationTests extends AbstractExpressionTests {
|
||||
evaluate("2>4?(3>2?true:false):(5<3?true:false)", false, Boolean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ternaryOperator06() {
|
||||
evaluate("3?:#var=5", 3, Integer.class);
|
||||
evaluate("null?:#var=5", 5, Integer.class);
|
||||
evaluate("2>4?(3>2?true:false):(5<3?true:false)", false, Boolean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ternaryExpressionWithImplicitGrouping() {
|
||||
evaluate("4 % 2 == 0 ? 2 : 3 * 10", 2, Integer.class);
|
||||
evaluate("4 % 2 == 1 ? 2 : 3 * 10", 30, Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ternaryExpressionWithExplicitGrouping() {
|
||||
evaluate("((4 % 2 == 0) ? 2 : 1) * 10", 20, Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ternaryOperatorWithNullValue() {
|
||||
assertThatExceptionOfType(EvaluationException.class).isThrownBy(
|
||||
|
||||
+103
-92
@@ -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.
|
||||
@@ -24,374 +24,380 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Parse some expressions and check we get the AST we expect. Rather than inspecting each node in the AST, we ask it to
|
||||
* write itself to a string form and check that is as expected.
|
||||
* Parse some expressions and check we get the AST we expect.
|
||||
*
|
||||
* <p>Rather than inspecting each node in the AST, we ask it to write itself to
|
||||
* a string form and check that is as expected.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class ParsingTests {
|
||||
class ParsingTests {
|
||||
|
||||
private final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
private SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
// literals
|
||||
@Test
|
||||
public void testLiteralBoolean01() {
|
||||
void literalBoolean01() {
|
||||
parseCheck("false");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteralLong01() {
|
||||
void literalLong01() {
|
||||
parseCheck("37L", "37");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteralBoolean02() {
|
||||
void literalBoolean02() {
|
||||
parseCheck("true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteralBoolean03() {
|
||||
void literalBoolean03() {
|
||||
parseCheck("!true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteralInteger01() {
|
||||
void literalInteger01() {
|
||||
parseCheck("1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteralInteger02() {
|
||||
void literalInteger02() {
|
||||
parseCheck("1415");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteralString01() {
|
||||
void literalString01() {
|
||||
parseCheck("'hello'");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteralString02() {
|
||||
void literalString02() {
|
||||
parseCheck("'joe bloggs'");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteralString03() {
|
||||
void literalString03() {
|
||||
parseCheck("'Tony''s Pizza'", "'Tony's Pizza'");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteralReal01() {
|
||||
void literalReal01() {
|
||||
parseCheck("6.0221415E+23", "6.0221415E23");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteralHex01() {
|
||||
void literalHex01() {
|
||||
parseCheck("0x7FFFFFFF", "2147483647");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteralDate01() {
|
||||
void literalDate01() {
|
||||
parseCheck("date('1974/08/24')");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteralDate02() {
|
||||
void literalDate02() {
|
||||
parseCheck("date('19740824T131030','yyyyMMddTHHmmss')");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteralNull01() {
|
||||
void literalNull01() {
|
||||
parseCheck("null");
|
||||
}
|
||||
|
||||
// boolean operators
|
||||
@Test
|
||||
public void testBooleanOperatorsOr01() {
|
||||
void booleanOperatorsOr01() {
|
||||
parseCheck("false or false", "(false or false)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBooleanOperatorsOr02() {
|
||||
void booleanOperatorsOr02() {
|
||||
parseCheck("false or true", "(false or true)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBooleanOperatorsOr03() {
|
||||
void booleanOperatorsOr03() {
|
||||
parseCheck("true or false", "(true or false)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBooleanOperatorsOr04() {
|
||||
void booleanOperatorsOr04() {
|
||||
parseCheck("true or false", "(true or false)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBooleanOperatorsMix01() {
|
||||
void booleanOperatorsMix01() {
|
||||
parseCheck("false or true and false", "(false or (true and false))");
|
||||
}
|
||||
|
||||
// relational operators
|
||||
@Test
|
||||
public void testRelOperatorsGT01() {
|
||||
void relOperatorsGT01() {
|
||||
parseCheck("3>6", "(3 > 6)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelOperatorsLT01() {
|
||||
void relOperatorsLT01() {
|
||||
parseCheck("3<6", "(3 < 6)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelOperatorsLE01() {
|
||||
void relOperatorsLE01() {
|
||||
parseCheck("3<=6", "(3 <= 6)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelOperatorsGE01() {
|
||||
void relOperatorsGE01() {
|
||||
parseCheck("3>=6", "(3 >= 6)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelOperatorsGE02() {
|
||||
void relOperatorsGE02() {
|
||||
parseCheck("3>=3", "(3 >= 3)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testElvis() {
|
||||
parseCheck("3?:1", "3 ?: 1");
|
||||
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)");
|
||||
}
|
||||
|
||||
// public void testRelOperatorsIn01() {
|
||||
// void relOperatorsIn01() {
|
||||
// parseCheck("3 in {1,2,3,4,5}", "(3 in {1,2,3,4,5})");
|
||||
// }
|
||||
//
|
||||
// public void testRelOperatorsBetween01() {
|
||||
// void relOperatorsBetween01() {
|
||||
// parseCheck("1 between {1, 5}", "(1 between {1,5})");
|
||||
// }
|
||||
|
||||
// public void testRelOperatorsBetween02() {
|
||||
// void relOperatorsBetween02() {
|
||||
// parseCheck("'efg' between {'abc', 'xyz'}", "('efg' between {'abc','xyz'})");
|
||||
// }// true
|
||||
|
||||
@Test
|
||||
public void testRelOperatorsIs01() {
|
||||
void relOperatorsIs01() {
|
||||
parseCheck("'xyz' instanceof int", "('xyz' instanceof int)");
|
||||
}// false
|
||||
|
||||
// public void testRelOperatorsIs02() {
|
||||
// void relOperatorsIs02() {
|
||||
// parseCheck("{1, 2, 3, 4, 5} instanceof List", "({1,2,3,4,5} instanceof List)");
|
||||
// }// true
|
||||
|
||||
@Test
|
||||
public void testRelOperatorsMatches01() {
|
||||
void relOperatorsMatches01() {
|
||||
parseCheck("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "('5.0067' matches '^-?\\d+(\\.\\d{2})?$')");
|
||||
}// false
|
||||
|
||||
@Test
|
||||
public void testRelOperatorsMatches02() {
|
||||
void relOperatorsMatches02() {
|
||||
parseCheck("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "('5.00' matches '^-?\\d+(\\.\\d{2})?$')");
|
||||
}// true
|
||||
|
||||
// mathematical operators
|
||||
@Test
|
||||
public void testMathOperatorsAdd01() {
|
||||
void mathOperatorsAdd01() {
|
||||
parseCheck("2+4", "(2 + 4)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMathOperatorsAdd02() {
|
||||
void mathOperatorsAdd02() {
|
||||
parseCheck("'a'+'b'", "('a' + 'b')");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMathOperatorsAdd03() {
|
||||
void mathOperatorsAdd03() {
|
||||
parseCheck("'hello'+' '+'world'", "(('hello' + ' ') + 'world')");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMathOperatorsSubtract01() {
|
||||
void mathOperatorsSubtract01() {
|
||||
parseCheck("5-4", "(5 - 4)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMathOperatorsMultiply01() {
|
||||
void mathOperatorsMultiply01() {
|
||||
parseCheck("7*4", "(7 * 4)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMathOperatorsDivide01() {
|
||||
void mathOperatorsDivide01() {
|
||||
parseCheck("8/4", "(8 / 4)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMathOperatorModulus01() {
|
||||
void mathOperatorModulus01() {
|
||||
parseCheck("7 % 4", "(7 % 4)");
|
||||
}
|
||||
|
||||
// mixed operators
|
||||
@Test
|
||||
public void testMixedOperators01() {
|
||||
void mixedOperators01() {
|
||||
parseCheck("true and 5>3", "(true and (5 > 3))");
|
||||
}
|
||||
|
||||
// collection processors
|
||||
// public void testCollectionProcessorsCount01() {
|
||||
// void collectionProcessorsCount01() {
|
||||
// parseCheck("new String[] {'abc','def','xyz'}.count()");
|
||||
// }
|
||||
|
||||
// public void testCollectionProcessorsCount02() {
|
||||
// void collectionProcessorsCount02() {
|
||||
// parseCheck("new int[] {1,2,3}.count()");
|
||||
// }
|
||||
//
|
||||
// public void testCollectionProcessorsMax01() {
|
||||
// void collectionProcessorsMax01() {
|
||||
// parseCheck("new int[] {1,2,3}.max()");
|
||||
// }
|
||||
//
|
||||
// public void testCollectionProcessorsMin01() {
|
||||
// void collectionProcessorsMin01() {
|
||||
// parseCheck("new int[] {1,2,3}.min()");
|
||||
// }
|
||||
//
|
||||
// public void testCollectionProcessorsAverage01() {
|
||||
// void collectionProcessorsAverage01() {
|
||||
// parseCheck("new int[] {1,2,3}.average()");
|
||||
// }
|
||||
//
|
||||
// public void testCollectionProcessorsSort01() {
|
||||
// void collectionProcessorsSort01() {
|
||||
// parseCheck("new int[] {3,2,1}.sort()");
|
||||
// }
|
||||
//
|
||||
// public void testCollectionProcessorsNonNull01() {
|
||||
// void collectionProcessorsNonNull01() {
|
||||
// parseCheck("{'a','b',null,'d',null}.nonNull()");
|
||||
// }
|
||||
//
|
||||
// public void testCollectionProcessorsDistinct01() {
|
||||
// void collectionProcessorsDistinct01() {
|
||||
// parseCheck("{'a','b','a','d','e'}.distinct()");
|
||||
// }
|
||||
|
||||
// references
|
||||
@Test
|
||||
public void testReferences01() {
|
||||
void references01() {
|
||||
parseCheck("@foo");
|
||||
parseCheck("@'foo.bar'");
|
||||
parseCheck("@\"foo.bar.goo\"","@'foo.bar.goo'");
|
||||
parseCheck("@\"foo.bar.goo\"" , "@'foo.bar.goo'");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReferences03() {
|
||||
void references03() {
|
||||
parseCheck("@$$foo");
|
||||
}
|
||||
|
||||
// properties
|
||||
@Test
|
||||
public void testProperties01() {
|
||||
void properties01() {
|
||||
parseCheck("name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProperties02() {
|
||||
void properties02() {
|
||||
parseCheck("placeofbirth.CitY");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProperties03() {
|
||||
void properties03() {
|
||||
parseCheck("a.b.c.d.e");
|
||||
}
|
||||
|
||||
// inline list creation
|
||||
@Test
|
||||
public void testInlineListCreation01() {
|
||||
void inlineListCreation01() {
|
||||
parseCheck("{1, 2, 3, 4, 5}", "{1,2,3,4,5}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInlineListCreation02() {
|
||||
void inlineListCreation02() {
|
||||
parseCheck("{'abc','xyz'}", "{'abc','xyz'}");
|
||||
}
|
||||
|
||||
// inline map creation
|
||||
@Test
|
||||
public void testInlineMapCreation01() {
|
||||
void inlineMapCreation01() {
|
||||
parseCheck("{'key1':'Value 1','today':DateTime.Today}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInlineMapCreation02() {
|
||||
void inlineMapCreation02() {
|
||||
parseCheck("{1:'January',2:'February',3:'March'}");
|
||||
}
|
||||
|
||||
// methods
|
||||
@Test
|
||||
public void testMethods01() {
|
||||
void methods01() {
|
||||
parseCheck("echo(12)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethods02() {
|
||||
void methods02() {
|
||||
parseCheck("echo(name)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethods03() {
|
||||
void methods03() {
|
||||
parseCheck("age.doubleItAndAdd(12)");
|
||||
}
|
||||
|
||||
// constructors
|
||||
@Test
|
||||
public void testConstructors01() {
|
||||
void constructors01() {
|
||||
parseCheck("new String('hello')");
|
||||
}
|
||||
|
||||
// public void testConstructors02() {
|
||||
// void constructors02() {
|
||||
// parseCheck("new String[3]");
|
||||
// }
|
||||
|
||||
// array construction
|
||||
// public void testArrayConstruction01() {
|
||||
// void arrayConstruction01() {
|
||||
// parseCheck("new int[] {1, 2, 3, 4, 5}", "new int[] {1,2,3,4,5}");
|
||||
// }
|
||||
//
|
||||
// public void testArrayConstruction02() {
|
||||
// void arrayConstruction02() {
|
||||
// parseCheck("new String[] {'abc','xyz'}", "new String[] {'abc','xyz'}");
|
||||
// }
|
||||
|
||||
// variables and functions
|
||||
@Test
|
||||
public void testVariables01() {
|
||||
void variables01() {
|
||||
parseCheck("#foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFunctions01() {
|
||||
void functions01() {
|
||||
parseCheck("#fn(1,2,3)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFunctions02() {
|
||||
void functions02() {
|
||||
parseCheck("#fn('hello')");
|
||||
}
|
||||
|
||||
// projections and selections
|
||||
// public void testProjections01() {
|
||||
// void projections01() {
|
||||
// parseCheck("{1,2,3,4,5,6,7,8,9,10}.!{#isEven()}");
|
||||
// }
|
||||
|
||||
// public void testSelections01() {
|
||||
// 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')}");
|
||||
// }
|
||||
|
||||
// public void testSelectionsFirst01() {
|
||||
// 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')}");
|
||||
// }
|
||||
|
||||
// public void testSelectionsLast01() {
|
||||
// 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
|
||||
public void testAssignmentToVariables01() {
|
||||
void assignmentToVariables01() {
|
||||
parseCheck("#var1='value1'");
|
||||
}
|
||||
|
||||
@@ -399,38 +405,43 @@ public class ParsingTests {
|
||||
// ternary operator
|
||||
|
||||
@Test
|
||||
public void testTernaryOperator01() {
|
||||
parseCheck("1>2?3:4","(1 > 2) ? 3 : 4");
|
||||
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)");
|
||||
}
|
||||
|
||||
// public void testTernaryOperator01() {
|
||||
// parseCheck("{1}.#isEven(#this) == 'y'?'it is even':'it is odd'",
|
||||
// "({1}.#isEven(#this) == 'y') ? 'it is even' : 'it is odd'");
|
||||
// }
|
||||
@Test
|
||||
void ternaryOperator02() {
|
||||
parseCheck("{1}.#isEven(#this) == 'y'?'it is even':'it is odd'",
|
||||
"(({1}.#isEven(#this) == 'y') ? 'it is even' : 'it is odd')");
|
||||
}
|
||||
|
||||
//
|
||||
// public void testLambdaMax() {
|
||||
// void lambdaMax() {
|
||||
// parseCheck("(#max = {|x,y| $x > $y ? $x : $y }; #max(5,25))", "(#max={|x,y| ($x > $y) ? $x : $y };#max(5,25))");
|
||||
// }
|
||||
//
|
||||
// public void testLambdaFactorial() {
|
||||
// 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
|
||||
public void testTypeReferences01() {
|
||||
void typeReferences01() {
|
||||
parseCheck("T(java.lang.String)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypeReferences02() {
|
||||
void typeReferences02() {
|
||||
parseCheck("T(String)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInlineList1() {
|
||||
void inlineList1() {
|
||||
parseCheck("{1,2,3,4}");
|
||||
}
|
||||
|
||||
@@ -440,7 +451,7 @@ public class ParsingTests {
|
||||
*
|
||||
* @param expression the expression to parse *and* the expected value of the string form of the resultant AST
|
||||
*/
|
||||
public void parseCheck(String expression) {
|
||||
private void parseCheck(String expression) {
|
||||
parseCheck(expression, expression);
|
||||
}
|
||||
|
||||
@@ -451,7 +462,7 @@ public class ParsingTests {
|
||||
* @param expression the expression to parse
|
||||
* @param expectedStringFormOfAST the expected string form of the AST
|
||||
*/
|
||||
public void parseCheck(String expression, String expectedStringFormOfAST) {
|
||||
private void parseCheck(String expression, String expectedStringFormOfAST) {
|
||||
SpelExpression e = parser.parseRaw(expression);
|
||||
assertThat(e).isNotNull();
|
||||
assertThat(e.toStringAST()).isEqualTo(expectedStringFormOfAST);
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
///CLOVER:OFF
|
||||
/**
|
||||
* Spring Security scenarios from https://wiki.springsource.com/display/SECURITY/Spring+Security+Expression-based+Authorization
|
||||
* Spring Security scenarios from https://docs.spring.io/spring-security/reference/servlet/authorization/expression-based.html
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
|
||||
+16
-11
@@ -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.
|
||||
@@ -78,9 +78,9 @@ public abstract class NamedParameterUtils {
|
||||
* Parse the SQL statement and locate any placeholders or named parameters.
|
||||
* Named parameters are substituted for a JDBC placeholder.
|
||||
* @param sql the SQL statement
|
||||
* @return the parsed statement, represented as ParsedSql instance
|
||||
* @return the parsed statement, represented as {@link ParsedSql} instance
|
||||
*/
|
||||
public static ParsedSql parseSqlStatement(final String sql) {
|
||||
public static ParsedSql parseSqlStatement(String sql) {
|
||||
Assert.notNull(sql, "SQL must not be null");
|
||||
|
||||
Set<String> namedParameters = new HashSet<>();
|
||||
@@ -122,17 +122,20 @@ public abstract class NamedParameterUtils {
|
||||
while (statement[j] != '}') {
|
||||
j++;
|
||||
if (j >= statement.length) {
|
||||
throw new InvalidDataAccessApiUsageException("Non-terminated named parameter declaration " +
|
||||
"at position " + i + " in statement: " + sql);
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
"Non-terminated named parameter declaration at position " + i +
|
||||
" in statement: " + sql);
|
||||
}
|
||||
if (statement[j] == ':' || statement[j] == '{') {
|
||||
throw new InvalidDataAccessApiUsageException("Parameter name contains invalid character '" +
|
||||
statement[j] + "' at position " + i + " in statement: " + sql);
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
"Parameter name contains invalid character '" + statement[j] +
|
||||
"' at position " + i + " in statement: " + sql);
|
||||
}
|
||||
}
|
||||
if (j - i > 2) {
|
||||
parameter = sql.substring(i + 2, j);
|
||||
namedParameterCount = addNewNamedParameter(namedParameters, namedParameterCount, parameter);
|
||||
namedParameterCount = addNewNamedParameter(
|
||||
namedParameters, namedParameterCount, parameter);
|
||||
totalParameterCount = addNamedParameter(
|
||||
parameterList, totalParameterCount, escapes, i, j + 1, parameter);
|
||||
}
|
||||
@@ -144,7 +147,8 @@ public abstract class NamedParameterUtils {
|
||||
}
|
||||
if (j - i > 1) {
|
||||
parameter = sql.substring(i + 1, j);
|
||||
namedParameterCount = addNewNamedParameter(namedParameters, namedParameterCount, parameter);
|
||||
namedParameterCount = addNewNamedParameter(
|
||||
namedParameters, namedParameterCount, parameter);
|
||||
totalParameterCount = addNamedParameter(
|
||||
parameterList, totalParameterCount, escapes, i, j, parameter);
|
||||
}
|
||||
@@ -185,8 +189,8 @@ public abstract class NamedParameterUtils {
|
||||
return parsedSql;
|
||||
}
|
||||
|
||||
private static int addNamedParameter(
|
||||
List<ParameterHolder> parameterList, int totalParameterCount, int escapes, int i, int j, String parameter) {
|
||||
private static int addNamedParameter(List<ParameterHolder> parameterList,
|
||||
int totalParameterCount, int escapes, int i, int j, String parameter) {
|
||||
|
||||
parameterList.add(new ParameterHolder(parameter, i - escapes, j - escapes));
|
||||
totalParameterCount++;
|
||||
@@ -271,6 +275,7 @@ public abstract class NamedParameterUtils {
|
||||
if (paramNames.isEmpty()) {
|
||||
return originalSql;
|
||||
}
|
||||
|
||||
StringBuilder actualSql = new StringBuilder(originalSql.length());
|
||||
int lastIndex = 0;
|
||||
for (int i = 0; i < paramNames.size(); i++) {
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Convenient super class for JDBC-based data access objects.
|
||||
* Convenient superclass for JDBC-based data access objects.
|
||||
*
|
||||
* <p>Requires a {@link javax.sql.DataSource} to be set, providing a
|
||||
* {@link org.springframework.jdbc.core.JdbcTemplate} based on it to
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ package org.springframework.jdbc.datasource.embedded;
|
||||
*/
|
||||
public enum EmbeddedDatabaseType {
|
||||
|
||||
/** The <a href="http://hsqldb.org">Hypersonic</a> Embedded Java SQL Database. */
|
||||
/** The <a href="https://hsqldb.org">Hypersonic</a> Embedded Java SQL Database. */
|
||||
HSQL,
|
||||
|
||||
/** The <a href="https://h2database.com">H2</a> Embedded Java SQL Database Engine. */
|
||||
|
||||
+26
-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.
|
||||
@@ -319,4 +319,29 @@ public class NamedParameterUtilsTests {
|
||||
assertThat(psql2.getParameterNames().get(0)).isEqualTo("xxx");
|
||||
}
|
||||
|
||||
@Test // gh-27925
|
||||
void namedParamMapReference() {
|
||||
String sql = "insert into foos (id) values (:headers[id])";
|
||||
ParsedSql psql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
assertThat(psql.getNamedParameterCount()).isEqualTo(1);
|
||||
assertThat(psql.getParameterNames()).containsExactly("headers[id]");
|
||||
|
||||
class Foo {
|
||||
final Map<String, Object> headers = new HashMap<>();
|
||||
public Foo() {
|
||||
this.headers.put("id", 1);
|
||||
}
|
||||
public Map<String, Object> getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
}
|
||||
|
||||
Foo foo = new Foo();
|
||||
Object[] params = NamedParameterUtils.buildValueArray(psql,
|
||||
new BeanPropertySqlParameterSource(foo), null);
|
||||
|
||||
assertThat(params[0]).isInstanceOf(SqlParameterValue.class);
|
||||
assertThat(((SqlParameterValue) params[0]).getValue()).isEqualTo(foo.getHeaders().get("id"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user