Compare commits

..

1 Commits

Author SHA1 Message Date
Spring Builds 5ea8ba1b96 Release v5.3.23 2022-09-15 07:47:35 +00:00
281 changed files with 2300 additions and 3706 deletions
@@ -9,5 +9,5 @@ jobs:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1
-1
View File
@@ -24,7 +24,6 @@ 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 -1
View File
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=8.0.352-librca
java=8.0.345-librca
+24 -27
View File
@@ -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.86.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.27"
mavenBom "io.netty:netty-bom:4.1.82.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.23"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
mavenBom "io.rsocket:rsocket-bom:1.1.3"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.50.v20221201"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.48.v20220622"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.5.32"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2"
mavenBom "org.jetbrains.kotlinx:kotlinx-serialization-bom:1.2.2"
mavenBom "org.junit:junit-bom:5.8.2"
}
dependencies {
dependencySet(group: 'org.apache.logging.log4j', version: '2.19.0') {
dependencySet(group: 'org.apache.logging.log4j', version: '2.18.0') {
entry 'log4j-api'
entry 'log4j-core'
entry 'log4j-jul'
@@ -68,7 +68,7 @@ configure(allprojects) { project ->
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
dependency "io.reactivex.rxjava2:rxjava:2.2.21"
dependency "io.reactivex.rxjava3:rxjava:3.1.5"
dependency "io.smallrye.reactive:mutiny:1.8.0"
dependency "io.smallrye.reactive:mutiny:1.7.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.30"
dependency "com.github.librepdf:openpdf:1.3.29"
dependency "com.rometools:rome:1.18.0"
dependency "commons-io:commons-io:2.5"
dependency "io.vavr:vavr:0.10.4"
@@ -128,18 +128,18 @@ configure(allprojects) { project ->
dependency "org.webjars:webjars-locator-core:0.48"
dependency "org.webjars:underscorejs:1.8.3"
dependencySet(group: 'org.apache.tomcat', version: '9.0.70') {
dependencySet(group: 'org.apache.tomcat', version: '9.0.65') {
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.70') {
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.65') {
entry 'tomcat-embed-core'
entry 'tomcat-embed-websocket'
}
dependencySet(group: 'io.undertow', version: '2.2.22.Final') {
dependencySet(group: 'io.undertow', version: '2.2.19.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.13"
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.12"
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") {
@@ -191,14 +191,14 @@ configure(allprojects) { project ->
dependency "org.junit.support:testng-engine:1.0.4"
dependency "org.hamcrest:hamcrest:2.2"
dependency "org.awaitility:awaitility:3.1.6"
dependency "org.assertj:assertj-core:3.24.1"
dependency "org.assertj:assertj-core:3.23.0"
dependencySet(group: 'org.xmlunit', version: '2.9.0') {
entry 'xmlunit-assertj'
entry('xmlunit-matchers') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
}
dependencySet(group: 'org.mockito', version: '4.9.0') { // spring-beans tests fail with 4.10+
dependencySet(group: 'org.mockito', version: '4.6.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.69.0") {
dependency("net.sourceforge.htmlunit:htmlunit:2.64.0") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:htmlunit-driver:2.67.0") {
dependency("org.seleniumhq.selenium:htmlunit-driver:2.64.0") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
@@ -340,7 +340,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "10.6.0"
toolVersion = "10.3.3"
configDirectory.set(rootProject.file("src/checkstyle"))
}
@@ -413,9 +413,11 @@ 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}/spring-core-test" + "${rootDir}/buildSrc" + "${rootDir}/framework-docs"
def projectDirs = allprojects.collect { it.projectDir } + "${rootDir}/buildSrc"
projectDirs.forEach { dir ->
[ 'bin', 'build', 'out', '.settings' ]
.collect { rootPath.relativize(new File(dir, it).toPath()) }
@@ -426,10 +428,6 @@ configure(rootProject) {
}
}
tasks.named("checkstyleNohttp").configure {
maxHeapSize = "1g"
}
publishing {
publications {
mavenJava(MavenPublication) {
@@ -439,5 +437,4 @@ configure(rootProject) {
}
}
}
}
-3
View File
@@ -15,6 +15,3 @@ changelog:
sort: "title"
labels:
- "type: dependency-upgrade"
contributors:
exclude:
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll"]
+2 -1
View File
@@ -1,10 +1,11 @@
FROM ubuntu:focal-20220922
FROM ubuntu:focal-20220826
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
RUN ./setup.sh java8
ENV JAVA_HOME /opt/openjdk/java8
ENV JDK11 /opt/openjdk/java11
ENV JDK17 /opt/openjdk/java17
ENV PATH $JAVA_HOME/bin:$PATH
+6
View File
@@ -5,9 +5,15 @@ case "$1" in
java8)
echo "https://github.com/bell-sw/Liberica/releases/download/8u345+1/bellsoft-jdk8u345+1-linux-amd64.tar.gz"
;;
java11)
echo "https://github.com/bell-sw/Liberica/releases/download/11.0.16+8/bellsoft-jdk11.0.16+8-linux-amd64.tar.gz"
;;
java17)
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.4+8/bellsoft-jdk17.0.4+8-linux-amd64.tar.gz"
;;
java18)
echo "https://github.com/bell-sw/Liberica/releases/download/18.0.2+10/bellsoft-jdk18.0.2+10-linux-amd64.tar.gz"
;;
*)
echo $"Unknown java version"
exit 1
+1 -1
View File
@@ -20,7 +20,7 @@ curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/c
mkdir -p /opt/openjdk
pushd /opt/openjdk > /dev/null
for jdk in java8 java17
for jdk in java8 java11 java17
do
JDK_URL=$( /get-jdk-url.sh $jdk )
mkdir $jdk
+38 -2
View File
@@ -125,6 +125,14 @@ resources:
access_token: ((github-ci-status-token))
branch: ((branch))
context: build
- name: repo-status-jdk11-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: jdk11-build
- name: repo-status-jdk17-build
type: github-status-resource
icon: eye-check-outline
@@ -229,6 +237,34 @@ jobs:
"zip.type": "schema"
get_params:
threads: 8
- name: jdk11-build
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
- get: every-morning
trigger: true
- put: repo-status-jdk11-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: check-project
image: ci-image
file: git-repo/ci/tasks/check-project.yml
privileged: true
timeout: ((task-timeout))
params:
TEST_TOOLCHAIN: 11
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-jdk11-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-jdk11-build
params: { state: "success", commit: "git-repo" }
- name: jdk17-build
serial: true
public: true
@@ -246,7 +282,7 @@ jobs:
privileged: true
timeout: ((task-timeout))
params:
TEST_TOOLCHAIN: 17
TEST_TOOLCHAIN: 15
<<: *build-project-task-params
on_failure:
do:
@@ -435,7 +471,7 @@ jobs:
groups:
- name: "builds"
jobs: ["build", "jdk17-build"]
jobs: ["build", "jdk11-build", "jdk17-build"]
- name: "releases"
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
- name: "ci-images"
+1 -1
View File
@@ -4,6 +4,6 @@ set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17 \
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK11,JDK15 \
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check
popd > /dev/null
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.25
version=5.3.23
org.gradle.jvmargs=-Xmx2048m
org.gradle.caching=true
org.gradle.parallel=true
+15 -4
View File
@@ -81,10 +81,6 @@ plugins.withType(JavaPlugin) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = testLanguageVersion
}
if(testLanguageVersion == JavaLanguageVersion.of(17)) {
jvmArgs(["--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.util=ALL-UNNAMED"])
}
}
}
}
@@ -134,6 +130,21 @@ pluginManager.withPlugin("kotlin") {
}
}
}
if (testToolchainConfigured()) {
def testLanguageVersion = testToolchainLanguageVersion()
def compiler = javaToolchains.compilerFor {
languageVersion = testLanguageVersion
}
// See https://kotlinlang.org/docs/gradle.html#attributes-specific-for-jvm
def javaVersion = testLanguageVersion.toString() == '8' ? '1.8' : testLanguageVersion.toString()
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
jdkHome = compiler.get().metadata.installationPath.asFile.absolutePath
}
}
}
}
// Configure the JMH plugin to use the toolchain for generating and running JMH bytecode
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+2 -2
View File
@@ -7,8 +7,8 @@ pluginManagement {
}
plugins {
id "com.gradle.enterprise" version "3.11.1"
id "io.spring.ge.conventions" version "0.0.11"
id "com.gradle.enterprise" version "3.10.2"
id "io.spring.ge.conventions" version "0.0.10"
}
include "spring-aop"
@@ -62,7 +62,7 @@ import org.springframework.util.StringUtils;
* <p>Mainly for internal use within the framework, but to some degree also
* useful for application classes. Consider
* <a href="https://commons.apache.org/proper/commons-beanutils/">Apache Commons BeanUtils</a>,
* <a href="https://github.com/ExpediaGroup/bull">BULL - Bean Utils Light Library</a>,
* <a href="https://hotelsdotcom.github.io/bull/">BULL - Bean Utils Light Library</a>,
* or similar third-party frameworks for more comprehensive bean utilities.
*
* @author Rod Johnson
@@ -97,6 +97,8 @@ 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);
@@ -295,7 +297,7 @@ public final class CachedIntrospectionResults {
// Only allow URL attribute introspection, not content resolution
continue;
}
if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType(), beanClass)) {
if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType())) {
// Ignore read-only properties such as ClassLoader - no need to bind to those
continue;
}
@@ -345,8 +347,7 @@ 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(), beanClass)) {
if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType())) {
// Ignore read-only properties such as ClassLoader - no need to bind to those
continue;
}
@@ -379,7 +380,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(), method.getDeclaringClass())) {
isInvalidReadOnlyPropertyType(method.getReturnType())) {
return false;
}
try {
@@ -392,11 +393,10 @@ public final class CachedIntrospectionResults {
}
}
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))));
private boolean isInvalidReadOnlyPropertyType(@Nullable Class<?> returnType) {
return (returnType != null && (AutoCloseable.class.isAssignableFrom(returnType) ||
ClassLoader.class.isAssignableFrom(returnType) ||
ProtectionDomain.class.isAssignableFrom(returnType)));
}
@@ -422,7 +422,7 @@ public final class CachedIntrospectionResults {
}
PropertyDescriptor[] getPropertyDescriptors() {
return this.propertyDescriptors.values().toArray(PropertyDescriptorUtils.EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
return this.propertyDescriptors.values().toArray(EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
}
private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class<?> beanClass, PropertyDescriptor pd) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 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,15 +17,9 @@
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;
@@ -38,80 +32,6 @@ 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}.
*/
@@ -253,72 +173,4 @@ 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;
}
}
}
@@ -1,71 +0,0 @@
/*
* 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.BeanDescriptor;
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 BeanDescriptor getBeanDescriptor() {
return new BeanDescriptor(beanClass);
}
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
return pds.toArray(PropertyDescriptorUtils.EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
}
};
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 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,6 +438,7 @@ 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());
@@ -562,9 +563,7 @@ 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 - " +
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -161,7 +161,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
/** Cache of pre-filtered post-processors. */
@Nullable
private BeanPostProcessorCache beanPostProcessorCache;
private volatile BeanPostProcessorCache beanPostProcessorCache;
/** Map from scope identifier String to corresponding Scope. */
private final Map<String, Scope> scopes = new LinkedHashMap<>(8);
@@ -944,12 +944,10 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
@Override
public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) {
Assert.notNull(beanPostProcessor, "BeanPostProcessor must not be null");
synchronized (this.beanPostProcessors) {
// Remove from old position, if any
this.beanPostProcessors.remove(beanPostProcessor);
// Add to end of list
this.beanPostProcessors.add(beanPostProcessor);
}
// Remove from old position, if any
this.beanPostProcessors.remove(beanPostProcessor);
// Add to end of list
this.beanPostProcessors.add(beanPostProcessor);
}
/**
@@ -959,12 +957,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
* @see #addBeanPostProcessor
*/
public void addBeanPostProcessors(Collection<? extends BeanPostProcessor> beanPostProcessors) {
synchronized (this.beanPostProcessors) {
// Remove from old position, if any
this.beanPostProcessors.removeAll(beanPostProcessors);
// Add to end of list
this.beanPostProcessors.addAll(beanPostProcessors);
}
this.beanPostProcessors.removeAll(beanPostProcessors);
this.beanPostProcessors.addAll(beanPostProcessors);
}
@Override
@@ -986,34 +980,26 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
* @since 5.3
*/
BeanPostProcessorCache getBeanPostProcessorCache() {
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);
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);
}
}
this.beanPostProcessorCache = bppCache;
if (bp instanceof DestructionAwareBeanPostProcessor) {
bpCache.destructionAware.add((DestructionAwareBeanPostProcessor) bp);
}
if (bp instanceof MergedBeanDefinitionPostProcessor) {
bpCache.mergedDefinition.add((MergedBeanDefinitionPostProcessor) bp);
}
}
return bppCache;
}
}
private void resetBeanPostProcessorCache() {
synchronized (this.beanPostProcessors) {
this.beanPostProcessorCache = null;
this.beanPostProcessorCache = bpCache;
}
return bpCache;
}
/**
@@ -2028,27 +2014,27 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
@Override
public BeanPostProcessor set(int index, BeanPostProcessor element) {
BeanPostProcessor result = super.set(index, element);
resetBeanPostProcessorCache();
beanPostProcessorCache = null;
return result;
}
@Override
public boolean add(BeanPostProcessor o) {
boolean success = super.add(o);
resetBeanPostProcessorCache();
beanPostProcessorCache = null;
return success;
}
@Override
public void add(int index, BeanPostProcessor element) {
super.add(index, element);
resetBeanPostProcessorCache();
beanPostProcessorCache = null;
}
@Override
public BeanPostProcessor remove(int index) {
BeanPostProcessor result = super.remove(index);
resetBeanPostProcessorCache();
beanPostProcessorCache = null;
return result;
}
@@ -2056,7 +2042,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
public boolean remove(Object o) {
boolean success = super.remove(o);
if (success) {
resetBeanPostProcessorCache();
beanPostProcessorCache = null;
}
return success;
}
@@ -2065,7 +2051,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
public boolean removeAll(Collection<?> c) {
boolean success = super.removeAll(c);
if (success) {
resetBeanPostProcessorCache();
beanPostProcessorCache = null;
}
return success;
}
@@ -2074,7 +2060,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
public boolean retainAll(Collection<?> c) {
boolean success = super.retainAll(c);
if (success) {
resetBeanPostProcessorCache();
beanPostProcessorCache = null;
}
return success;
}
@@ -2083,7 +2069,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
public boolean addAll(Collection<? extends BeanPostProcessor> c) {
boolean success = super.addAll(c);
if (success) {
resetBeanPostProcessorCache();
beanPostProcessorCache = null;
}
return success;
}
@@ -2092,7 +2078,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) {
resetBeanPostProcessorCache();
beanPostProcessorCache = null;
}
return success;
}
@@ -2101,7 +2087,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
public boolean removeIf(Predicate<? super BeanPostProcessor> filter) {
boolean success = super.removeIf(filter);
if (success) {
resetBeanPostProcessorCache();
beanPostProcessorCache = null;
}
return success;
}
@@ -2109,7 +2095,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
@Override
public void replaceAll(UnaryOperator<BeanPostProcessor> operator) {
super.replaceAll(operator);
resetBeanPostProcessorCache();
beanPostProcessorCache = null;
}
}
@@ -77,21 +77,6 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests {
assertThat(accessor.getPropertyValue("aliasedName")).isEqualTo("tom");
}
@Test
void replaceWrappedInstance() {
GetterBean target = new GetterBean();
BeanWrapperImpl accessor = createAccessor(target);
accessor.setPropertyValue("name", "tom");
assertThat(target.getAliasedName()).isEqualTo("tom");
assertThat(accessor.getPropertyValue("aliasedName")).isEqualTo("tom");
target = new GetterBean();
accessor.setWrappedInstance(target);
accessor.setPropertyValue("name", "tom");
assertThat(target.getAliasedName()).isEqualTo("tom");
assertThat(accessor.getPropertyValue("aliasedName")).isEqualTo("tom");
}
@Test
void setValidAndInvalidPropertyValuesShouldContainExceptionDetails() {
TestBean target = new TestBean();
@@ -168,16 +153,6 @@ 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();
@@ -231,10 +206,6 @@ 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
@@ -377,36 +348,6 @@ 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;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.util.ClassUtils;
@@ -148,7 +148,7 @@ public class QualifierAnnotationAutowireBeanFactoryTests {
lbf.registerBeanDefinition(MARK, person2);
MethodParameter param = new MethodParameter(QualifiedTestBean.class.getDeclaredConstructor(Person.class), 0);
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(param, false);
param.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
param.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
assertThat(param.getParameterName()).isEqualTo("tpb");
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue();
assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isTrue();
@@ -174,9 +174,9 @@ public class QualifierAnnotationAutowireBeanFactoryTests {
new MethodParameter(QualifiedTestBean.class.getDeclaredMethod("autowireNonqualified", Person.class), 0);
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(qualifiedParam, false);
DependencyDescriptor nonqualifiedDescriptor = new DependencyDescriptor(nonqualifiedParam, false);
qualifiedParam.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
qualifiedParam.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
assertThat(qualifiedParam.getParameterName()).isEqualTo("tpb");
nonqualifiedParam.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
nonqualifiedParam.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
assertThat(nonqualifiedParam.getParameterName()).isEqualTo("tpb");
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue();
assertThat(lbf.isAutowireCandidate(JUERGEN, nonqualifiedDescriptor)).isTrue();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -502,7 +502,7 @@ class CustomEditorTests {
CharBean cb = new CharBean();
BeanWrapper bw = new BeanWrapperImpl(cb);
bw.setPropertyValue("myChar", 'c');
bw.setPropertyValue("myChar", new Character('c'));
assertThat(cb.getMyChar()).isEqualTo('c');
bw.setPropertyValue("myChar", "c");
@@ -526,16 +526,16 @@ class CustomEditorTests {
bw.registerCustomEditor(Character.class, new CharacterEditor(true));
bw.setPropertyValue("myCharacter", 'c');
assertThat(cb.getMyCharacter()).isEqualTo('c');
assertThat(cb.getMyCharacter()).isEqualTo(Character.valueOf('c'));
bw.setPropertyValue("myCharacter", "c");
assertThat(cb.getMyCharacter()).isEqualTo('c');
assertThat(cb.getMyCharacter()).isEqualTo(Character.valueOf('c'));
bw.setPropertyValue("myCharacter", "\u0041");
assertThat(cb.getMyCharacter()).isEqualTo('A');
assertThat(cb.getMyCharacter()).isEqualTo(Character.valueOf('A'));
bw.setPropertyValue("myCharacter", " ");
assertThat(cb.getMyCharacter()).isEqualTo(' ');
assertThat(cb.getMyCharacter()).isEqualTo(Character.valueOf(' '));
bw.setPropertyValue("myCharacter", "");
assertThat(cb.getMyCharacter()).isNull();
@@ -722,7 +722,7 @@ class CustomEditorTests {
@Test
void testCustomDateEditor() {
CustomDateEditor editor = new CustomDateEditor(new SimpleDateFormat("MM/dd/yyyy"), false);
CustomDateEditor editor = new CustomDateEditor(null, 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(new SimpleDateFormat("MM/dd/yyyy"), true);
CustomDateEditor editor = new CustomDateEditor(null, true);
editor.setValue(null);
assertThat(editor.getValue()).isNull();
assertThat(editor.getAsText()).isEmpty();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2018 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.
@@ -93,7 +93,7 @@ public class SimpleMailMessage implements MailMessage, Serializable {
@Override
public void setFrom(@Nullable String from) {
public void setFrom(String from) {
this.from = from;
}
@@ -103,7 +103,7 @@ public class SimpleMailMessage implements MailMessage, Serializable {
}
@Override
public void setReplyTo(@Nullable String replyTo) {
public void setReplyTo(String replyTo) {
this.replyTo = replyTo;
}
@@ -113,7 +113,7 @@ public class SimpleMailMessage implements MailMessage, Serializable {
}
@Override
public void setTo(@Nullable String to) {
public void setTo(String to) {
this.to = new String[] {to};
}
@@ -128,12 +128,12 @@ public class SimpleMailMessage implements MailMessage, Serializable {
}
@Override
public void setCc(@Nullable String cc) {
public void setCc(String cc) {
this.cc = new String[] {cc};
}
@Override
public void setCc(@Nullable String... cc) {
public void setCc(String... cc) {
this.cc = cc;
}
@@ -143,12 +143,12 @@ public class SimpleMailMessage implements MailMessage, Serializable {
}
@Override
public void setBcc(@Nullable String bcc) {
public void setBcc(String bcc) {
this.bcc = new String[] {bcc};
}
@Override
public void setBcc(@Nullable String... bcc) {
public void setBcc(String... bcc) {
this.bcc = bcc;
}
@@ -158,7 +158,7 @@ public class SimpleMailMessage implements MailMessage, Serializable {
}
@Override
public void setSentDate(@Nullable Date sentDate) {
public void setSentDate(Date sentDate) {
this.sentDate = sentDate;
}
@@ -168,7 +168,7 @@ public class SimpleMailMessage implements MailMessage, Serializable {
}
@Override
public void setSubject(@Nullable String subject) {
public void setSubject(String subject) {
this.subject = subject;
}
@@ -178,7 +178,7 @@ public class SimpleMailMessage implements MailMessage, Serializable {
}
@Override
public void setText(@Nullable String text) {
public void setText(String text) {
this.text = text;
}
@@ -77,23 +77,6 @@ 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) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 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,8 +96,6 @@ 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);
@@ -106,6 +104,7 @@ public class Jsr310DateTimeFormatAnnotationFormatterFactory extends EmbeddedValu
}
}
DateTimeFormatter formatter = getFormatter(annotation, fieldType);
return new TemporalAccessorParser((Class<? extends TemporalAccessor>) fieldType,
formatter, resolvedFallbackPatterns.toArray(new String[0]), annotation);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 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,8 +71,7 @@ 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;
@@ -101,8 +100,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;
@@ -23,7 +23,6 @@ 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;
@@ -85,10 +84,4 @@ 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();
}
}
@@ -30,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Stephane Nicoll
*/
class Gh29105Tests {
public class Gh29105Tests {
@Test
void beanProviderWithParentContextReuseOrder() {
@@ -47,7 +47,6 @@ class Gh29105Tests {
List<Class<?>> orderedTypes = child.getBeanProvider(MyService.class)
.orderedStream().map(Object::getClass).collect(Collectors.toList());
assertThat(orderedTypes).containsExactly(CustomService.class, DefaultService.class);
child.close();
}
@@ -79,5 +78,4 @@ class Gh29105Tests {
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import java.beans.PropertyDescriptor;
import javax.management.DynamicMBean;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
@@ -31,7 +32,6 @@ import org.springframework.beans.BeanWrapperImpl;
import org.springframework.jmx.IJmxTestBean;
import org.springframework.jmx.JmxTestBean;
import org.springframework.jmx.export.TestDynamicMBean;
import org.springframework.util.MBeanTestUtils;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -41,7 +41,6 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Rob Harrop
* @author Juergen Hoeller
* @author Sam Brannen
*/
class JmxUtilsTests {
@@ -132,11 +131,10 @@ class JmxUtilsTests {
MBeanServer server = null;
try {
server = JmxUtils.locateMBeanServer();
assertThat(server).isNotNull();
}
finally {
if (server != null) {
MBeanTestUtils.releaseMBeanServer(server);
MBeanServerFactory.releaseMBeanServer(server);
}
}
}
@@ -21,25 +21,21 @@ import java.lang.reflect.Field;
import javax.naming.spi.NamingManager;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledForJreRange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.condition.JRE.JAVA_16;
/**
* Tests for {@link JndiLocatorDelegate}.
*
* @author Phillip Webb
* @author Juergen Hoeller
* @author Sam Brannen
*/
@DisabledForJreRange(
min = JAVA_16,
disabledReason = "Cannot use reflection to set private static field in javax.naming.spi.NamingManager")
class JndiLocatorDelegateTests {
public class JndiLocatorDelegateTests {
@Test
void isDefaultJndiEnvironmentAvailableFalse() throws Exception {
public void isDefaultJndiEnvironmentAvailableFalse() throws Exception {
Field builderField = NamingManager.class.getDeclaredField("initctx_factory_builder");
builderField.setAccessible(true);
Object oldBuilder = builderField.get(null);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,59 +18,37 @@ package org.springframework.util;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.util.EnumSet;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import org.junit.jupiter.api.condition.JRE;
/**
* Utilities for MBean tests.
*
* @author Phillip Webb
* @author Sam Brannen
*/
public class MBeanTestUtils {
/**
* Reset the {@link MBeanServerFactory} to a known consistent state. This involves
* {@linkplain #releaseMBeanServer(MBeanServer) releasing} all currently registered
* MBeanServers.
* <p>On JDK 8 - JDK 16, this method also resets the platformMBeanServer field
* in {@link ManagementFactory} to {@code null}.
* Resets MBeanServerFactory and ManagementFactory to a known consistent state.
* <p>This involves releasing all currently registered MBeanServers and resetting
* the platformMBeanServer to null.
*/
public static synchronized void resetMBeanServers() throws Exception {
for (MBeanServer server : MBeanServerFactory.findMBeanServer(null)) {
releaseMBeanServer(server);
}
if (!isCurrentJreWithinRange(JRE.JAVA_16, JRE.OTHER)) {
Field field = ManagementFactory.class.getDeclaredField("platformMBeanServer");
field.setAccessible(true);
field.set(null, null);
}
}
/**
* Attempt to release the supplied {@link MBeanServer}.
* <p>Ignores any {@link IllegalArgumentException} thrown by
* {@link MBeanServerFactory#releaseMBeanServer(MBeanServer)} whose error
* message contains the text "not in list".
*/
public static void releaseMBeanServer(MBeanServer server) {
try {
MBeanServerFactory.releaseMBeanServer(server);
}
catch (IllegalArgumentException ex) {
if (!ex.getMessage().contains("not in list")) {
throw ex;
try {
MBeanServerFactory.releaseMBeanServer(server);
}
catch (IllegalArgumentException ex) {
if (!ex.getMessage().contains("not in list")) {
throw ex;
}
}
}
}
static boolean isCurrentJreWithinRange(JRE min, JRE max) {
return EnumSet.range(min, max).contains(JRE.currentVersion());
Field field = ManagementFactory.class.getDeclaredField("platformMBeanServer");
field.setAccessible(true);
field.set(null, null);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,9 +18,9 @@ package org.springframework.core;
/**
* Default implementation of the {@link ParameterNameDiscoverer} strategy interface,
* using the Java 8 standard reflection mechanism, and falling back to the ASM-based
* {@link LocalVariableTableParameterNameDiscoverer} for checking debug information
* in the class file (e.g. for classes compiled with earlier Java versions).
* using the Java 8 standard reflection mechanism (if available), and falling back
* to the ASM-based {@link LocalVariableTableParameterNameDiscoverer} for checking
* debug information in the class file.
*
* <p>If a Kotlin reflection implementation is present,
* {@link KotlinReflectionParameterNameDiscoverer} is added first in the list and
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,13 +31,11 @@ import org.springframework.lang.Nullable;
* {@link ParameterNameDiscoverer} implementation which uses Kotlin's reflection facilities
* for introspecting parameter names.
*
* <p>Compared to {@link StandardReflectionParameterNameDiscoverer}, it allows in addition to
* Compared to {@link StandardReflectionParameterNameDiscoverer}, it allows in addition to
* determine interface parameter names without requiring Java 8 -parameters compiler flag.
*
* @author Sebastien Deleuze
* @since 5.0
* @see StandardReflectionParameterNameDiscoverer
* @see DefaultParameterNameDiscoverer
*/
public class KotlinReflectionParameterNameDiscoverer implements ParameterNameDiscoverer {
@@ -47,18 +47,12 @@ import org.springframework.util.ClassUtils;
* caches the ASM discovered information for each introspected Class, in a thread-safe
* manner. It is recommended to reuse ParameterNameDiscoverer instances as far as possible.
*
* <p>This discoverer variant is effectively superseded by the Java 8 based
* {@link StandardReflectionParameterNameDiscoverer} but included as a fallback still
* (for code not compiled with the standard "-parameters" compiler flag).
*
* @author Adrian Colyer
* @author Costin Leau
* @author Juergen Hoeller
* @author Chris Beams
* @author Sam Brannen
* @since 2.0
* @see StandardReflectionParameterNameDiscoverer
* @see DefaultParameterNameDiscoverer
*/
public class LocalVariableTableParameterNameDiscoverer implements ParameterNameDiscoverer {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,15 +26,10 @@ import org.springframework.lang.Nullable;
* {@link ParameterNameDiscoverer} implementation which uses JDK 8's reflection facilities
* for introspecting parameter names (based on the "-parameters" compiler flag).
*
* <p>This is a key element of {@link DefaultParameterNameDiscoverer} where it is being
* combined with {@link KotlinReflectionParameterNameDiscoverer} if Kotlin is present.
*
* @author Juergen Hoeller
* @since 4.0
* @see java.lang.reflect.Method#getParameters()
* @see java.lang.reflect.Parameter#getName()
* @see KotlinReflectionParameterNameDiscoverer
* @see DefaultParameterNameDiscoverer
*/
public class StandardReflectionParameterNameDiscoverer implements ParameterNameDiscoverer {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 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,16 +433,6 @@ 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;
@@ -735,16 +725,6 @@ 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;
@@ -776,23 +756,7 @@ public abstract class AnnotatedElementUtils {
private static MergedAnnotations getRepeatableAnnotations(AnnotatedElement element,
@Nullable Class<? extends Annotation> containerType, Class<? extends Annotation> annotationType) {
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);
}
RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType);
return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS, repeatableContainers);
}
@@ -803,23 +767,7 @@ public abstract class AnnotatedElementUtils {
private static MergedAnnotations findRepeatableAnnotations(AnnotatedElement element,
@Nullable Class<? extends Annotation> containerType, Class<? extends Annotation> annotationType) {
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);
}
RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType);
return MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY, repeatableContainers);
}
@@ -829,7 +777,8 @@ 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
@@ -839,12 +788,13 @@ 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 holds specific annotations.
* Adapted {@link AnnotatedElement} that hold specific annotations.
*/
private static class AnnotatedElementForAnnotations implements AnnotatedElement {
@@ -32,6 +32,7 @@ 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;
/**
@@ -240,7 +241,7 @@ final class AnnotationTypeMapping {
mapping.claimedAliases.addAll(aliases);
if (mapping.annotation != null) {
int[] resolvedMirrors = mapping.mirrorSets.resolve(null,
mapping.annotation, AnnotationUtils::invokeAnnotationMethod);
mapping.annotation, ReflectionUtils::invokeMethod);
for (int i = 0; i < mapping.attributes.size(); i++) {
if (aliases.contains(mapping.attributes.get(i))) {
this.annotationValueMappings[attributeIndex] = resolvedMirrors[i];
@@ -504,7 +505,7 @@ final class AnnotationTypeMapping {
if (source == this && metaAnnotationsOnly) {
return null;
}
return AnnotationUtils.invokeAnnotationMethod(source.attributes.get(mappedIndex), source.annotation);
return ReflectionUtils.invokeMethod(source.attributes.get(mappedIndex), source.annotation);
}
/**
@@ -593,7 +594,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 = AnnotationUtils.invokeAnnotationMethod(attribute, annotation);
Object value1 = ReflectionUtils.invokeMethod(attribute, annotation);
Object value2;
if (extractedValue instanceof TypeMappedAnnotation) {
value2 = ((TypeMappedAnnotation<?>) extractedValue).getValue(attribute.getName()).orElse(null);
@@ -19,10 +19,9 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
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;
@@ -1053,42 +1052,23 @@ public abstract class AnnotationUtils {
}
try {
Method method = annotation.annotationType().getDeclaredMethod(attributeName);
return invokeAnnotationMethod(method, annotation);
ReflectionUtils.makeAccessible(method);
return method.invoke(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,
@@ -86,6 +86,17 @@ final class AttributeMethods {
}
/**
* Determine if this instance only contains a single attribute named
* {@code value}.
* @return {@code true} if there is only a value attribute
*/
boolean hasOnlyValueAttribute() {
return (this.attributeMethods.length == 1 &&
MergedAnnotation.VALUE.equals(this.attributeMethods[0].getName()));
}
/**
* Determine if values from the given annotation can be safely accessed without
* causing any {@link TypeNotPresentException TypeNotPresentExceptions}.
@@ -98,7 +109,7 @@ final class AttributeMethods {
for (int i = 0; i < size(); i++) {
if (canThrowTypeNotPresentException(i)) {
try {
AnnotationUtils.invokeAnnotationMethod(get(i), annotation);
get(i).invoke(annotation);
}
catch (Throwable ex) {
return false;
@@ -123,7 +134,7 @@ final class AttributeMethods {
for (int i = 0; i < size(); i++) {
if (canThrowTypeNotPresentException(i)) {
try {
AnnotationUtils.invokeAnnotationMethod(get(i), annotation);
get(i).invoke(annotation);
}
catch (Throwable ex) {
throw new IllegalStateException("Could not obtain annotation attribute value for " +
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import org.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
@@ -38,7 +39,6 @@ import org.springframework.util.ObjectUtils;
* <p>To completely disable repeatable support use {@link #none()}.
*
* @author Phillip Webb
* @author Sam Brannen
* @since 5.2
*/
public abstract class RepeatableContainers {
@@ -101,19 +101,15 @@ public abstract class RepeatableContainers {
}
/**
* 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,
* 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,
* 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) {
@@ -152,7 +148,7 @@ public abstract class RepeatableContainers {
Annotation[] findRepeatedAnnotations(Annotation annotation) {
Method method = getRepeatedAnnotationsMethod(annotation.annotationType());
if (method != null) {
return (Annotation[]) AnnotationUtils.invokeAnnotationMethod(method, annotation);
return (Annotation[]) ReflectionUtils.invokeMethod(method, annotation);
}
return super.findRepeatedAnnotations(annotation);
}
@@ -166,8 +162,8 @@ public abstract class RepeatableContainers {
private static Object computeRepeatedAnnotationsMethod(Class<? extends Annotation> annotationType) {
AttributeMethods methods = AttributeMethods.forAnnotationType(annotationType);
Method method = methods.get(MergedAnnotation.VALUE);
if (method != null) {
if (methods.hasOnlyValueAttribute()) {
Method method = methods.get(0);
Class<?> returnType = method.getReturnType();
if (returnType.isArray()) {
Class<?> componentType = returnType.getComponentType();
@@ -239,7 +235,7 @@ public abstract class RepeatableContainers {
@Nullable
Annotation[] findRepeatedAnnotations(Annotation annotation) {
if (this.container.isAssignableFrom(annotation.annotationType())) {
return (Annotation[]) AnnotationUtils.invokeAnnotationMethod(this.valueMethod, annotation);
return (Annotation[]) ReflectionUtils.invokeMethod(this.valueMethod, annotation);
}
return super.findRepeatedAnnotations(annotation);
}
@@ -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 = AnnotationUtils.invokeAnnotationMethod(attribute, other);
Object otherValue = ReflectionUtils.invokeMethod(attribute, other);
if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
return false;
}
@@ -33,6 +33,7 @@ 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
@@ -42,9 +43,9 @@ import org.springframework.util.ClassUtils;
* {@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 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)}.
* 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)}.
*
* <p>Extracted root attribute values must be compatible with the attribute
* return type, namely:
@@ -433,7 +434,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
}
if (value == null) {
Method attribute = this.mapping.getAttributes().get(attributeIndex);
value = AnnotationUtils.invokeAnnotationMethod(attribute, this.mapping.getAnnotation());
value = ReflectionUtils.invokeMethod(attribute, this.mapping.getAnnotation());
}
return value;
}
@@ -554,7 +555,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
private ValueExtractor getValueExtractor(Object value) {
if (value instanceof Annotation) {
return AnnotationUtils::invokeAnnotationMethod;
return ReflectionUtils::invokeMethod;
}
if (value instanceof Map) {
return TypeMappedAnnotation::extractFromMap;
@@ -614,8 +615,7 @@ 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, AnnotationUtils::invokeAnnotationMethod, 0);
return new TypeMappedAnnotation<>(mappings.get(0), null, source, annotation, ReflectionUtils::invokeMethod, 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,
AnnotationUtils::invokeAnnotationMethod, aggregateIndex, logger);
ReflectionUtils::invokeMethod, aggregateIndex, logger);
}
@Nullable
@@ -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");
}
}
@@ -695,7 +695,7 @@ public abstract class DataBufferUtils {
@Override
public byte[] delimiter() {
Assert.state(this.longestDelimiter != NO_DELIMITER, "'delimiter' not set");
Assert.state(this.longestDelimiter != NO_DELIMITER, "Illegal state!");
return this.longestDelimiter;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 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<>(computeMapInitialCapacity(expectedSize), DEFAULT_LOAD_FACTOR);
return new HashMap<>((int) (expectedSize / DEFAULT_LOAD_FACTOR), DEFAULT_LOAD_FACTOR);
}
/**
@@ -102,11 +102,7 @@ public abstract class CollectionUtils {
* @see #newHashMap(int)
*/
public static <K, V> LinkedHashMap<K, V> newLinkedHashMap(int expectedSize) {
return new LinkedHashMap<>(computeMapInitialCapacity(expectedSize), DEFAULT_LOAD_FACTOR);
}
private static int computeMapInitialCapacity(int expectedSize) {
return (int) Math.ceil(expectedSize / (double) DEFAULT_LOAD_FACTOR);
return new LinkedHashMap<>((int) (expectedSize / DEFAULT_LOAD_FACTOR), DEFAULT_LOAD_FACTOR);
}
/**
@@ -1,34 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework;
import org.junit.platform.suite.api.IncludeClassNamePatterns;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
/**
* JUnit Platform based test suite for tests in the spring-core module.
*
* <p><strong>This suite is only intended to be used manually within an IDE.</strong>
*
* @author Sam Brannen
*/
@Suite
@SelectPackages({"org.springframework.core", "org.springframework.util"})
@IncludeClassNamePatterns(".*Tests?$")
class SpringCoreTestSuite {
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 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,6 +34,7 @@ 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;
@@ -155,7 +156,7 @@ class CollectionFactoryTests {
@Test
void createApproximateCollectionFromEmptyHashSet() {
Collection<String> set = createApproximateCollection(new HashSet<String>(), 2);
assertThat(set).isEmpty();
Assertions.assertThat(set).isEmpty();
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class LocalVariableTableParameterNameDiscovererTests {
private final ParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
private final LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
@Test
@@ -162,23 +162,23 @@ class LocalVariableTableParameterNameDiscovererTests {
Constructor<?> ctor = clazz.getDeclaredConstructor(Object.class);
String[] names = discoverer.getParameterNames(ctor);
assertThat(names).hasSize(1);
assertThat(names.length).isEqualTo(1);
assertThat(names[0]).isEqualTo("key");
ctor = clazz.getDeclaredConstructor(Object.class, Object.class);
names = discoverer.getParameterNames(ctor);
assertThat(names).hasSize(2);
assertThat(names.length).isEqualTo(2);
assertThat(names[0]).isEqualTo("key");
assertThat(names[1]).isEqualTo("value");
Method m = clazz.getMethod("generifiedStaticMethod", Object.class);
names = discoverer.getParameterNames(m);
assertThat(names).hasSize(1);
assertThat(names.length).isEqualTo(1);
assertThat(names[0]).isEqualTo("param");
m = clazz.getMethod("generifiedMethod", Object.class, long.class, Object.class, Object.class);
names = discoverer.getParameterNames(m);
assertThat(names).hasSize(4);
assertThat(names.length).isEqualTo(4);
assertThat(names[0]).isEqualTo("param");
assertThat(names[1]).isEqualTo("x");
assertThat(names[2]).isEqualTo("key");
@@ -186,21 +186,21 @@ class LocalVariableTableParameterNameDiscovererTests {
m = clazz.getMethod("voidStaticMethod", Object.class, long.class, int.class);
names = discoverer.getParameterNames(m);
assertThat(names).hasSize(3);
assertThat(names.length).isEqualTo(3);
assertThat(names[0]).isEqualTo("obj");
assertThat(names[1]).isEqualTo("x");
assertThat(names[2]).isEqualTo("i");
m = clazz.getMethod("nonVoidStaticMethod", Object.class, long.class, int.class);
names = discoverer.getParameterNames(m);
assertThat(names).hasSize(3);
assertThat(names.length).isEqualTo(3);
assertThat(names[0]).isEqualTo("obj");
assertThat(names[1]).isEqualTo("x");
assertThat(names[2]).isEqualTo("i");
m = clazz.getMethod("getDate");
names = discoverer.getParameterNames(m);
assertThat(names).isEmpty();
assertThat(names.length).isEqualTo(0);
}
@Disabled("Ignored because Ubuntu packages OpenJDK with debug symbols enabled. See SPR-8078.")
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,10 +43,11 @@ import static org.junit.jupiter.api.condition.JRE.JAVA_14;
@DisabledForJreRange(min = JAVA_14)
public class SpringCoreBlockHoundIntegrationTests {
@BeforeAll
static void setup() {
static void setUp() {
BlockHound.builder()
.with(new ReactorBlockHoundIntegration()) // Reactor non-blocking thread predicate
.with(new ReactorBlockHoundIntegration()) // Reactor non-blocking thread predicate
.with(new ReactiveAdapterRegistry.SpringCoreBlockHoundIntegration())
.install();
}
@@ -20,7 +20,6 @@ import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -78,7 +77,6 @@ import static org.springframework.core.annotation.AnnotationUtilsTests.asArray;
* @see AnnotationUtilsTests
* @see MultipleComposedAnnotationsOnSingleAnnotatedElementTests
* @see ComposedRepeatableAnnotationsTests
* @see NestedRepeatableAnnotationsTests
*/
class AnnotatedElementUtilsTests {
@@ -910,31 +908,6 @@ class AnnotatedElementUtilsTests {
assertThat(annotation.value()).containsExactly("FromValueAttributeMeta");
}
/**
* @since 5.3.25
*/
@Test // gh-29685
void getMergedRepeatableAnnotationsWithContainerWithMultipleAttributes() {
Set<StandardRepeatableWithContainerWithMultipleAttributes> repeatableAnnotations =
AnnotatedElementUtils.getMergedRepeatableAnnotations(
StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class,
StandardRepeatableWithContainerWithMultipleAttributes.class);
assertThat(repeatableAnnotations).map(StandardRepeatableWithContainerWithMultipleAttributes::value)
.containsExactly("a", "b");
}
/**
* @since 5.3.25
*/
@Test // gh-29685
void findMergedRepeatableAnnotationsWithContainerWithMultipleAttributes() {
Set<StandardRepeatableWithContainerWithMultipleAttributes> repeatableAnnotations =
AnnotatedElementUtils.findMergedRepeatableAnnotations(
StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class,
StandardRepeatableWithContainerWithMultipleAttributes.class);
assertThat(repeatableAnnotations).map(StandardRepeatableWithContainerWithMultipleAttributes::value)
.containsExactly("a", "b");
}
// -------------------------------------------------------------------------
@@ -1584,24 +1557,4 @@ class AnnotatedElementUtilsTests {
static class ValueAttributeMetaMetaClass {
}
@Retention(RetentionPolicy.RUNTIME)
@interface StandardContainerWithMultipleAttributes {
StandardRepeatableWithContainerWithMultipleAttributes[] value();
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(StandardContainerWithMultipleAttributes.class)
@interface StandardRepeatableWithContainerWithMultipleAttributes {
String value() default "";
}
@StandardRepeatableWithContainerWithMultipleAttributes("a")
@StandardRepeatableWithContainerWithMultipleAttributes("b")
static class StandardRepeatablesWithContainerWithMultipleAttributesTestCase {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,6 +58,24 @@ class AttributeMethodsTests {
assertThat(getAll(methods)).flatExtracting(Method::getName).containsExactly("intValue", "value");
}
@Test
void hasOnlyValueAttributeWhenHasOnlyValueAttributeReturnsTrue() {
AttributeMethods methods = AttributeMethods.forAnnotationType(ValueOnly.class);
assertThat(methods.hasOnlyValueAttribute()).isTrue();
}
@Test
void hasOnlyValueAttributeWhenHasOnlySingleNonValueAttributeReturnsFalse() {
AttributeMethods methods = AttributeMethods.forAnnotationType(NonValueOnly.class);
assertThat(methods.hasOnlyValueAttribute()).isFalse();
}
@Test
void hasOnlyValueAttributeWhenHasOnlyMultipleAttributesIncludingValueReturnsFalse() {
AttributeMethods methods = AttributeMethods.forAnnotationType(MultipleAttributes.class);
assertThat(methods.hasOnlyValueAttribute()).isFalse();
}
@Test
void indexOfNameReturnsIndex() {
AttributeMethods methods = AttributeMethods.forAnnotationType(MultipleAttributes.class);
@@ -1,212 +0,0 @@
/*
* 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();
}
}
}
@@ -20,9 +20,7 @@ import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -33,175 +31,193 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* Tests for {@link RepeatableContainers}.
*
* @author Phillip Webb
* @author Sam Brannen
*/
class RepeatableContainersTests {
@Nested
class StandardRepeatableContainersTests {
@Test
void standardRepeatablesWhenNonRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(),
NonRepeatableTestCase.class, NonRepeatable.class);
assertThat(values).isNull();
}
@Test
void standardRepeatablesWhenSingleReturnsNull() {
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(),
SingleStandardRepeatableTestCase.class, StandardRepeatable.class);
assertThat(values).isNull();
}
@Test
void standardRepeatablesWhenContainerButNotRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(),
ExplicitRepeatablesTestCase.class, ExplicitContainer.class);
assertThat(values).isNull();
}
@Test
void standardRepeatablesWhenContainerReturnsRepeats() {
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(),
StandardRepeatablesTestCase.class, StandardContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
void standardRepeatablesWithContainerWithMultipleAttributes() {
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(),
StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class,
StandardContainerWithMultipleAttributes.class);
assertThat(values).containsExactly("a", "b");
}
@Test
void standardRepeatablesWhenNonRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.standardRepeatables(), WithNonRepeatable.class,
NonRepeatable.class);
assertThat(values).isNull();
}
@Nested
class ExplicitRepeatableContainerTests {
@Test
void standardRepeatablesWhenSingleReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.standardRepeatables(),
WithSingleStandardRepeatable.class, StandardRepeatable.class);
assertThat(values).isNull();
}
@Test
void ofExplicitWhenNonRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class),
NonRepeatableTestCase.class, NonRepeatable.class);
assertThat(values).isNull();
}
@Test
void standardRepeatablesWhenContainerReturnsRepeats() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.standardRepeatables(), WithStandardRepeatables.class,
StandardContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
void ofExplicitWhenStandardRepeatableContainerReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class),
StandardRepeatablesTestCase.class, StandardContainer.class);
assertThat(values).isNull();
}
@Test
void standardRepeatablesWhenContainerButNotRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.standardRepeatables(), WithExplicitRepeatables.class,
ExplicitContainer.class);
assertThat(values).isNull();
}
@Test
void ofExplicitWhenContainerReturnsRepeats() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class),
ExplicitRepeatablesTestCase.class, ExplicitContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
void ofExplicitWhenNonRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class),
WithNonRepeatable.class, NonRepeatable.class);
assertThat(values).isNull();
}
@Test
void ofExplicitWhenContainerIsNullDeducesContainer() {
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.of(StandardRepeatable.class, null),
StandardRepeatablesTestCase.class, StandardContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
void ofExplicitWhenStandardRepeatableContainerReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class),
WithStandardRepeatables.class, StandardContainer.class);
assertThat(values).isNull();
}
@Test
void ofExplicitWhenHasNoValueThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> RepeatableContainers.of(ExplicitRepeatable.class, InvalidNoValue.class))
.withMessageContaining("Invalid declaration of container type [%s] for repeatable annotation [%s]",
InvalidNoValue.class.getName(), ExplicitRepeatable.class.getName());
}
@Test
void ofExplicitWhenContainerReturnsRepeats() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class),
WithExplicitRepeatables.class, ExplicitContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
void ofExplicitWhenValueIsNotArrayThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> RepeatableContainers.of(ExplicitRepeatable.class, InvalidNotArray.class))
.withMessage("Container type [%s] must declare a 'value' attribute for an array of type [%s]",
InvalidNotArray.class.getName(), ExplicitRepeatable.class.getName());
}
@Test
void ofExplicitWhenHasNoValueThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() ->
RepeatableContainers.of(ExplicitRepeatable.class, InvalidNoValue.class))
.withMessageContaining("Invalid declaration of container type ["
+ InvalidNoValue.class.getName()
+ "] for repeatable annotation ["
+ ExplicitRepeatable.class.getName() + "]");
}
@Test
void ofExplicitWhenValueIsArrayOfWrongTypeThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> RepeatableContainers.of(ExplicitRepeatable.class, InvalidWrongArrayType.class))
.withMessage("Container type [%s] must declare a 'value' attribute for an array of type [%s]",
InvalidWrongArrayType.class.getName(), ExplicitRepeatable.class.getName());
}
@Test
void ofExplicitWhenValueIsNotArrayThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() ->
RepeatableContainers.of(ExplicitRepeatable.class, InvalidNotArray.class))
.withMessage("Container type ["
+ InvalidNotArray.class.getName()
+ "] must declare a 'value' attribute for an array of type ["
+ ExplicitRepeatable.class.getName() + "]");
}
@Test
void ofExplicitWhenAnnotationIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RepeatableContainers.of(null, null))
.withMessage("Repeatable must not be null");
}
@Test
void ofExplicitWhenValueIsArrayOfWrongTypeThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() ->
RepeatableContainers.of(ExplicitRepeatable.class, InvalidWrongArrayType.class))
.withMessage("Container type ["
+ InvalidWrongArrayType.class.getName()
+ "] must declare a 'value' attribute for an array of type ["
+ ExplicitRepeatable.class.getName() + "]");
}
@Test
void ofExplicitWhenContainerIsNullAndNotRepeatableThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RepeatableContainers.of(ExplicitRepeatable.class, null))
.withMessage("Annotation type must be a repeatable annotation: failed to resolve container type for %s",
ExplicitRepeatable.class.getName());
}
@Test
void ofExplicitWhenAnnotationIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() ->
RepeatableContainers.of(null, null))
.withMessage("Repeatable must not be null");
}
@Test
void ofExplicitWhenContainerIsNullDeducesContainer() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(StandardRepeatable.class, null),
WithStandardRepeatables.class, StandardContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
void ofExplicitWhenContainerIsNullAndNotRepeatableThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() ->
RepeatableContainers.of(ExplicitRepeatable.class, null))
.withMessage("Annotation type must be a repeatable annotation: " +
"failed to resolve container type for " +
ExplicitRepeatable.class.getName());
}
@Test
void standardAndExplicitReturnsRepeats() {
RepeatableContainers repeatableContainers = RepeatableContainers.standardRepeatables()
.and(ExplicitContainer.class, ExplicitRepeatable.class);
assertThat(findRepeatedAnnotationValues(repeatableContainers, StandardRepeatablesTestCase.class, StandardContainer.class))
.containsExactly("a", "b");
assertThat(findRepeatedAnnotationValues(repeatableContainers, ExplicitRepeatablesTestCase.class, ExplicitContainer.class))
.containsExactly("a", "b");
RepeatableContainers repeatableContainers = RepeatableContainers.standardRepeatables().and(
ExplicitContainer.class, ExplicitRepeatable.class);
assertThat(findRepeatedAnnotationValues(repeatableContainers,
WithStandardRepeatables.class, StandardContainer.class)).containsExactly(
"a", "b");
assertThat(findRepeatedAnnotationValues(repeatableContainers,
WithExplicitRepeatables.class, ExplicitContainer.class)).containsExactly(
"a", "b");
}
@Test
void noneAlwaysReturnsNull() {
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.none(), StandardRepeatablesTestCase.class,
StandardContainer.class);
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.none(), WithStandardRepeatables.class,
StandardContainer.class);
assertThat(values).isNull();
}
@Test
void equalsAndHashcode() {
RepeatableContainers c1 = RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class);
RepeatableContainers c2 = RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class);
RepeatableContainers c1 = RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class);
RepeatableContainers c2 = RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class);
RepeatableContainers c3 = RepeatableContainers.standardRepeatables();
RepeatableContainers c4 = RepeatableContainers.standardRepeatables().and(ExplicitContainer.class, ExplicitRepeatable.class);
assertThat(c1).hasSameHashCodeAs(c2);
RepeatableContainers c4 = RepeatableContainers.standardRepeatables().and(
ExplicitContainer.class, ExplicitRepeatable.class);
assertThat(c1.hashCode()).isEqualTo(c2.hashCode());
assertThat(c1).isEqualTo(c1).isEqualTo(c2);
assertThat(c1).isNotEqualTo(c3).isNotEqualTo(c4);
}
private static Object[] findRepeatedAnnotationValues(RepeatableContainers containers,
private Object[] findRepeatedAnnotationValues(RepeatableContainers containers,
Class<?> element, Class<? extends Annotation> annotationType) {
Annotation[] annotations = containers.findRepeatedAnnotations(element.getAnnotation(annotationType));
Annotation[] annotations = containers.findRepeatedAnnotations(
element.getAnnotation(annotationType));
return extractValues(annotations);
}
private static Object[] extractValues(Annotation[] annotations) {
if (annotations == null) {
return null;
private Object[] extractValues(Annotation[] annotations) {
try {
if (annotations == null) {
return null;
}
Object[] result = new String[annotations.length];
for (int i = 0; i < annotations.length; i++) {
result[i] = annotations[i].annotationType().getMethod("value").invoke(
annotations[i]);
}
return result;
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
return Arrays.stream(annotations).map(AnnotationUtils::getValue).toArray(Object[]::new);
}
@Retention(RetentionPolicy.RUNTIME)
@interface NonRepeatable {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(StandardContainer.class)
@interface StandardRepeatable {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@interface StandardContainer {
@@ -209,8 +225,7 @@ class RepeatableContainersTests {
}
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(StandardContainer.class)
@interface StandardRepeatable {
@interface ExplicitRepeatable {
String value() default "";
}
@@ -221,12 +236,6 @@ class RepeatableContainersTests {
ExplicitRepeatable[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@interface ExplicitRepeatable {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@interface InvalidNoValue {
}
@@ -244,40 +253,20 @@ class RepeatableContainersTests {
}
@NonRepeatable("a")
static class NonRepeatableTestCase {
static class WithNonRepeatable {
}
@StandardRepeatable("a")
static class SingleStandardRepeatableTestCase {
static class WithSingleStandardRepeatable {
}
@StandardRepeatable("a")
@StandardRepeatable("b")
static class StandardRepeatablesTestCase {
}
@Retention(RetentionPolicy.RUNTIME)
@interface StandardContainerWithMultipleAttributes {
StandardRepeatableWithContainerWithMultipleAttributes[] value();
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(StandardContainerWithMultipleAttributes.class)
@interface StandardRepeatableWithContainerWithMultipleAttributes {
String value() default "";
}
@StandardRepeatableWithContainerWithMultipleAttributes("a")
@StandardRepeatableWithContainerWithMultipleAttributes("b")
static class StandardRepeatablesWithContainerWithMultipleAttributesTestCase {
static class WithStandardRepeatables {
}
@ExplicitContainer({ @ExplicitRepeatable("a"), @ExplicitRepeatable("b") })
static class ExplicitRepeatablesTestCase {
static class WithExplicitRepeatables {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 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.
@@ -63,7 +63,7 @@ class ResourceRegionEncoderTests extends AbstractLeakCheckingTests {
}
@Test
void shouldEncodeResourceRegionFileResource() {
void shouldEncodeResourceRegionFileResource() throws Exception {
ResourceRegion region = new ResourceRegion(
new ClassPathResource("ResourceRegionEncoderTests.txt", getClass()), 0, 6);
Flux<DataBuffer> result = this.encoder.encode(Mono.just(region), this.bufferFactory,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,8 @@ 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);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 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,19 +16,16 @@
package org.springframework.core.io.support;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
@@ -36,9 +33,8 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link PathMatchingResourcePatternResolver}.
*
* <p>If tests fail, uncomment the diagnostics in {@link #assertFilenames(String, boolean, String...)}.
* If this test case fails, uncomment diagnostics in the
* {@link #assertProtocolAndFilenames} method.
*
* @author Oliver Hutchison
* @author Juergen Hoeller
@@ -48,261 +44,124 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*/
class PathMatchingResourcePatternResolverTests {
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[] 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[] TEST_CLASSES_IN_CORE_IO_SUPPORT = { "PathMatchingResourcePatternResolverTests.class" };
private static final String[] TEST_CLASSES_IN_CORE_IO_SUPPORT =
new String[] {"PathMatchingResourcePatternResolverTests.class"};
private static final String[] CLASSES_IN_REACTOR_UTIL_ANNOTATION = { "NonNull.class", "NonNullApi.class", "Nullable.class" };
private static final String[] CLASSES_IN_REACTOR_UTIL_ANNOTATIONS =
new String[] {"NonNull.class", "NonNullApi.class", "Nullable.class"};
private PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
@Test
void invalidPrefixWithPatternElementInIt() throws IOException {
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");
}
@Nested
class InvalidPatterns {
@Test
void singleResourceInJar() throws IOException {
Resource[] resources = resolver.getResources("org/reactivestreams/Publisher.class");
assertThat(resources.length).isEqualTo(1);
assertProtocolAndFilenames(resources, "jar", "Publisher.class");
}
@Test
void invalidPrefixWithPatternElementInItThrowsException() {
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> resolver.getResources("xx**:**/*.xy"));
@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);
}
}
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");
}
@Test
void classpathWithPatternInJar() throws IOException {
Resource[] resources = resolver.getResources("classpath:reactor/util/annotation/*.class");
assertProtocolAndFilenames(resources, "jar", CLASSES_IN_REACTOR_UTIL_ANNOTATIONS);
}
@Test
void classpathStarWithPatternInJar() throws IOException {
Resource[] resources = resolver.getResources("classpath*:reactor/util/annotation/*.class");
assertProtocolAndFilenames(resources, "jar", CLASSES_IN_REACTOR_UTIL_ANNOTATIONS);
}
@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;
}
}
assertThat(found).as("Could not find aspectj_1_5_0.dtd in the root of the aspectjweaver jar").isTrue();
}
@Nested
class FileSystemResources {
private void assertProtocolAndFilenames(Resource[] resources, String protocol, String... filenames)
throws IOException {
@Test
void singleResourceOnFileSystem() {
String pattern = "org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.class";
assertExactFilenames(pattern, "PathMatchingResourcePatternResolverTests.class");
}
// 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]);
// }
@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$");
}
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);
}
}
@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");
}
}
private void assertFilenames(String pattern, String... filenames) {
assertFilenames(pattern, false, filenames);
}
private void assertExactFilenames(String pattern, String... filenames) {
assertFilenames(pattern, true, filenames);
}
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);
}
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
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();
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();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -251,28 +251,32 @@ class ObjectUtilsTests {
@Deprecated
void hashCodeWithDouble() {
double dbl = 9830.43;
assertThat(ObjectUtils.hashCode(dbl)).isEqualTo(Double.hashCode(dbl));
int expected = (new Double(dbl)).hashCode();
assertThat(ObjectUtils.hashCode(dbl)).isEqualTo(expected);
}
@Test
@Deprecated
void hashCodeWithFloat() {
float flt = 34.8f;
assertThat(ObjectUtils.hashCode(flt)).isEqualTo(Float.hashCode(flt));
int expected = (Float.valueOf(flt)).hashCode();
assertThat(ObjectUtils.hashCode(flt)).isEqualTo(expected);
}
@Test
@Deprecated
void hashCodeWithLong() {
long lng = 883L;
assertThat(ObjectUtils.hashCode(lng)).isEqualTo(Long.hashCode(lng));
int expected = (Long.valueOf(lng)).hashCode();
assertThat(ObjectUtils.hashCode(lng)).isEqualTo(expected);
}
@Test
void identityToString() {
Object obj = new Object();
String expected = obj.getClass().getName() + "@" + ObjectUtils.getIdentityHexString(obj);
assertThat(ObjectUtils.identityToString(obj)).isEqualTo(expected);
String actual = ObjectUtils.identityToString(obj);
assertThat(actual).isEqualTo(expected);
}
@Test
@@ -728,7 +732,7 @@ class ObjectUtilsTests {
@Test
void nullSafeToStringWithObjectArray() {
Object[] array = {"Han", 43};
Object[] array = {"Han", Long.valueOf(43)};
assertThat(ObjectUtils.nullSafeToString(array)).isEqualTo("{Han, 43}");
}
@@ -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,7 +123,8 @@ class SocketUtilsTests {
@Test
void findAvailableTcpPortsWithRequestedNumberGreaterThanSizeOfRange() {
assertThatIllegalArgumentException().isThrownBy(() -> findAvailableTcpPorts(50, 45000, 45010));
assertThatIllegalArgumentException().isThrownBy(() ->
findAvailableTcpPorts(50, 45000, 45010));
}
@@ -131,14 +132,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
@@ -154,8 +155,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");
}
@@ -197,7 +198,8 @@ class SocketUtilsTests {
@Test
void findAvailableUdpPortsWithRequestedNumberGreaterThanSizeOfRange() {
assertThatIllegalArgumentException().isThrownBy(() -> findAvailableUdpPorts(50, 45000, 45010));
assertThatIllegalArgumentException().isThrownBy(() ->
findAvailableUdpPorts(50, 45000, 45010));
}
@@ -224,13 +226,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).as("port").isBetween(minPort, maxPort);
assertThat(port >= minPort).as("port [" + port + "] >= " + minPort).isTrue();
assertThat(port <= maxPort).as("port [" + port + "] <= " + maxPort).isTrue();
}
private void assertAvailablePorts(SortedSet<Integer> ports, int numRequested, int minPort, int maxPort) {
assertThat(ports).as("number of ports requested").hasSize(numRequested);
assertThat(ports.size()).as("number of ports requested").isEqualTo(numRequested);
for (int port : ports) {
assertPortInRange(port, minPort, maxPort);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 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,6 +33,8 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link StreamUtils}.
@@ -55,47 +57,53 @@ class StreamUtilsTests {
@Test
void copyToByteArray() throws Exception {
InputStream inputStream = new ByteArrayInputStream(bytes);
InputStream inputStream = spy(new ByteArrayInputStream(bytes));
byte[] actual = StreamUtils.copyToByteArray(inputStream);
assertThat(actual).isEqualTo(bytes);
verify(inputStream, never()).close();
}
@Test
void copyToString() throws Exception {
Charset charset = Charset.defaultCharset();
InputStream inputStream = new ByteArrayInputStream(string.getBytes(charset));
InputStream inputStream = spy(new ByteArrayInputStream(string.getBytes(charset)));
String actual = StreamUtils.copyToString(inputStream, charset);
assertThat(actual).isEqualTo(string);
verify(inputStream, never()).close();
}
@Test
void copyBytes() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream out = spy(new ByteArrayOutputStream());
StreamUtils.copy(bytes, out);
assertThat(out.toByteArray()).isEqualTo(bytes);
verify(out, never()).close();
}
@Test
void copyString() throws Exception {
Charset charset = Charset.defaultCharset();
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream out = spy(new ByteArrayOutputStream());
StreamUtils.copy(string, charset, out);
assertThat(out.toByteArray()).isEqualTo(string.getBytes(charset));
verify(out, never()).close();
}
@Test
void copyStream() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream out = spy(new ByteArrayOutputStream());
StreamUtils.copy(new ByteArrayInputStream(bytes), out);
assertThat(out.toByteArray()).isEqualTo(bytes);
verify(out, never()).close();
}
@Test
void copyRange() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream out = spy(new ByteArrayOutputStream());
StreamUtils.copyRange(new ByteArrayInputStream(bytes), out, 0, 100);
byte[] range = Arrays.copyOfRange(bytes, 0, 101);
assertThat(out.toByteArray()).isEqualTo(range);
verify(out, never()).close();
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 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.assertThat
import org.assertj.core.api.Assertions
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)
assertThat(unbridged.isBridge).isFalse
assertThat(bridged.isBridge).isTrue
Assertions.assertThat(unbridged.isBridge).isFalse
Assertions.assertThat(bridged.isBridge).isTrue
assertThat(BridgeMethodResolver.findBridgedMethod(unbridged)).`as`("Unbridged method not returned directly").isEqualTo(unbridged)
assertThat(BridgeMethodResolver.findBridgedMethod(bridged)).`as`("Incorrect bridged method returned").isEqualTo(unbridged)
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)
}
@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)
assertThat(unbridged.isBridge).isFalse
assertThat(bridged.isBridge).isTrue
Assertions.assertThat(unbridged.isBridge).isFalse
Assertions.assertThat(bridged.isBridge).isTrue
assertThat(BridgeMethodResolver.findBridgedMethod(unbridged)).`as`("Unbridged method not returned directly").isEqualTo(unbridged)
assertThat(BridgeMethodResolver.findBridgedMethod(bridged)).`as`("Incorrect bridged method returned").isEqualTo(unbridged)
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)
}
}
@@ -1 +0,0 @@
test 1
@@ -1 +0,0 @@
test 2
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +16,6 @@
package org.springframework.core.testfixture.io.buffer;
import java.time.Duration;
import org.junit.jupiter.api.AfterEach;
import org.springframework.core.io.buffer.DataBufferFactory;
@@ -44,7 +42,7 @@ public abstract class AbstractLeakCheckingTests {
*/
@AfterEach
final void checkForLeaks() {
this.bufferFactory.checkForLeaks(Duration.ofSeconds(1));
this.bufferFactory.checkForLeaks();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -81,21 +81,13 @@ 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(timeout))) {
if (Instant.now().isBefore(start.plus(Duration.ofSeconds(5)))) {
try {
Thread.sleep(50);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
import org.springframework.asm.MethodVisitor;
import org.springframework.core.convert.TypeDescriptor;
@@ -47,15 +46,10 @@ import org.springframework.util.Assert;
* Represents the invocation of a constructor. Either a constructor on a regular type or
* construction of an array. When an array is constructed, an initializer can be specified.
*
* <h4>Examples</h4>
* <ul>
* <li><code>new example.Foo()</code></li>
* <li><code>new String('hello world')</code></li>
* <li><code>new int[] {1,2,3,4}</code></li>
* <li><code>new String[] {'abc','xyz'}</code></li>
* <li><code>new int[5]</code></li>
* <li><code>new int[3][4]</code></li>
* </ul>
* <p>Examples:<br>
* new String('hello world')<br>
* new int[]{1,2,3,4}<br>
* new int[3] new int[3]{1,2,3}
*
* @author Andy Clement
* @author Juergen Hoeller
@@ -74,7 +68,7 @@ public class ConstructorReference extends SpelNodeImpl {
private final boolean isArrayConstructor;
@Nullable
private final SpelNodeImpl[] dimensions;
private SpelNodeImpl[] dimensions;
// TODO is this caching safe - passing the expression around will mean this executor is also being passed around
/** The cached executor that may be reused on subsequent evaluations. */
@@ -89,7 +83,6 @@ public class ConstructorReference extends SpelNodeImpl {
public ConstructorReference(int startPos, int endPos, SpelNodeImpl... arguments) {
super(startPos, endPos, arguments);
this.isArrayConstructor = false;
this.dimensions = null;
}
/**
@@ -221,33 +214,16 @@ public class ConstructorReference extends SpelNodeImpl {
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder("new ");
sb.append(getChild(0).toStringAST()); // constructor or array type
// Arrays
if (this.isArrayConstructor) {
if (hasInitializer()) {
// new int[] {1, 2, 3, 4, 5}, etc.
InlineList initializer = (InlineList) getChild(1);
sb.append("[] ").append(initializer.toStringAST());
}
else {
// new int[3], new java.lang.String[3][4], etc.
for (SpelNodeImpl dimension : this.dimensions) {
sb.append('[').append(dimension.toStringAST()).append(']');
}
int index = 0;
sb.append(getChild(index++).toStringAST());
sb.append('(');
for (int i = index; i < getChildCount(); i++) {
if (i > index) {
sb.append(',');
}
sb.append(getChild(i).toStringAST());
}
// Constructors
else {
// new String('hello'), new org.example.Person('Jane', 32), etc.
StringJoiner sj = new StringJoiner(",", "(", ")");
int count = getChildCount();
for (int i = 1; i < count; i++) {
sj.add(getChild(i).toStringAST());
}
sb.append(sj.toString());
}
sb.append(')');
return sb.toString();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,6 @@ import org.springframework.util.ObjectUtils;
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
*/
public class Elvis extends SpelNodeImpl {
@@ -65,7 +64,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
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,7 +26,6 @@ import org.springframework.util.StringUtils;
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
*/
public class StringLiteral extends Literal {
@@ -37,19 +36,9 @@ public class StringLiteral extends Literal {
public StringLiteral(String payload, int startPos, int endPos, String value) {
super(payload, startPos, endPos);
// The original enclosing quote character for the string literal: ' or ".
char quoteCharacter = value.charAt(0);
// Remove enclosing quotes
String valueWithinQuotes = value.substring(1, value.length() - 1);
// Replace escaped internal quote characters
if (quoteCharacter == '\'') {
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "''", "'");
}
else {
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "\"\"", "\"");
}
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "''", "'");
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "\"\"", "\"");
this.value = new TypedValue(valueWithinQuotes);
this.exitTypeDescriptor = "Ljava/lang/String";
@@ -63,9 +52,7 @@ public class StringLiteral extends Literal {
@Override
public String toString() {
String ast = String.valueOf(getLiteralValue().getValue());
ast = StringUtils.replace(ast, "'", "''");
return "'" + ast + "'";
return "'" + getLiteralValue().getValue() + "'";
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,6 @@ import org.springframework.util.ObjectUtils;
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
*/
public class Ternary extends SpelNodeImpl {
@@ -63,7 +62,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() {
@@ -116,10 +116,6 @@ 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
@@ -364,53 +360,6 @@ class EvaluationTests extends AbstractExpressionTests {
}
@Nested
class StringLiterals {
@Test
void insideSingleQuotes() {
evaluate("'hello'", "hello", String.class);
evaluate("'hello world'", "hello world", String.class);
}
@Test
void insideDoubleQuotes() {
evaluate("\"hello\"", "hello", String.class);
evaluate("\"hello world\"", "hello world", String.class);
}
@Test
void singleQuotesInsideSingleQuotes() {
evaluate("'Tony''s Pizza'", "Tony's Pizza", String.class);
evaluate("'big ''''pizza'''' parlor'", "big ''pizza'' parlor", String.class);
}
@Test
void doubleQuotesInsideDoubleQuotes() {
evaluate("\"big \"\"pizza\"\" parlor\"", "big \"pizza\" parlor", String.class);
evaluate("\"big \"\"\"\"pizza\"\"\"\" parlor\"", "big \"\"pizza\"\" parlor", String.class);
}
@Test
void singleQuotesInsideDoubleQuotes() {
evaluate("\"Tony's Pizza\"", "Tony's Pizza", String.class);
evaluate("\"big ''pizza'' parlor\"", "big ''pizza'' parlor", String.class);
}
@Test
void doubleQuotesInsideSingleQuotes() {
evaluate("'big \"pizza\" parlor'", "big \"pizza\" parlor", String.class);
evaluate("'two double \"\" quotes'", "two double \"\" quotes", String.class);
}
@Test
void inCompoundExpressions() {
evaluate("'123''4' == '123''4'", true, Boolean.class);
evaluate("\"123\"\"4\" == \"123\"\"4\"", true, Boolean.class);
}
}
@Nested
class RelationalOperatorTests {
@@ -675,24 +624,6 @@ 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(
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 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,70 +29,52 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Andy Clement
*/
class LiteralExpressionTests {
private final LiteralExpression lEx = new LiteralExpression("somevalue");
public class LiteralExpressionTests {
@Test
void getValue() throws Exception {
assertThat(lEx.getValue()).isEqualTo("somevalue");
assertThat(lEx.getValue(String.class)).isEqualTo("somevalue");
assertThat(lEx.getValue(new Rooty())).isEqualTo("somevalue");
assertThat(lEx.getValue(new Rooty(), String.class)).isEqualTo("somevalue");
}
@Test
void getValueWithSuppliedEvaluationContext() throws Exception {
public void testGetValue() throws Exception {
LiteralExpression lEx = new LiteralExpression("somevalue");
assertThat(lEx.getValue()).isInstanceOf(String.class).isEqualTo("somevalue");
assertThat(lEx.getValue(String.class)).isInstanceOf(String.class).isEqualTo("somevalue");
EvaluationContext ctx = new StandardEvaluationContext();
assertThat(lEx.getValue(ctx)).isEqualTo("somevalue");
assertThat(lEx.getValue(ctx, String.class)).isEqualTo("somevalue");
assertThat(lEx.getValue(ctx, new Rooty())).isEqualTo("somevalue");
assertThat(lEx.getValue(ctx, new Rooty(), String.class)).isEqualTo("somevalue");
}
@Test
void getExpressionString() {
assertThat(lEx.getValue(ctx)).isInstanceOf(String.class).isEqualTo("somevalue");
assertThat(lEx.getValue(ctx, String.class)).isInstanceOf(String.class).isEqualTo("somevalue");
assertThat(lEx.getValue(new Rooty())).isInstanceOf(String.class).isEqualTo("somevalue");
assertThat(lEx.getValue(new Rooty(), String.class)).isInstanceOf(String.class).isEqualTo("somevalue");
assertThat(lEx.getValue(ctx, new Rooty())).isInstanceOf(String.class).isEqualTo("somevalue");
assertThat(lEx.getValue(ctx, new Rooty(),String.class)).isInstanceOf(String.class).isEqualTo("somevalue");
assertThat(lEx.getExpressionString()).isEqualTo("somevalue");
}
@Test
void isWritable() throws Exception {
assertThat(lEx.isWritable(new StandardEvaluationContext())).isFalse();
assertThat(lEx.isWritable(new Rooty())).isFalse();
assertThat(lEx.isWritable(new StandardEvaluationContext(), new Rooty())).isFalse();
}
static class Rooty {}
@Test
void setValue() {
assertThatExceptionOfType(EvaluationException.class)
.isThrownBy(() -> lEx.setValue(new StandardEvaluationContext(), "flibble"))
public void testSetValue() {
assertThatExceptionOfType(EvaluationException.class).isThrownBy(() ->
new LiteralExpression("somevalue").setValue(new StandardEvaluationContext(), "flibble"))
.satisfies(ex -> assertThat(ex.getExpressionString()).isEqualTo("somevalue"));
assertThatExceptionOfType(EvaluationException.class)
.isThrownBy(() -> lEx.setValue(new Rooty(), "flibble"))
assertThatExceptionOfType(EvaluationException.class).isThrownBy(() ->
new LiteralExpression("somevalue").setValue(new Rooty(), "flibble"))
.satisfies(ex -> assertThat(ex.getExpressionString()).isEqualTo("somevalue"));
assertThatExceptionOfType(EvaluationException.class)
.isThrownBy(() -> lEx.setValue(new StandardEvaluationContext(), new Rooty(), "flibble"))
assertThatExceptionOfType(EvaluationException.class).isThrownBy(() ->
new LiteralExpression("somevalue").setValue(new StandardEvaluationContext(), new Rooty(), "flibble"))
.satisfies(ex -> assertThat(ex.getExpressionString()).isEqualTo("somevalue"));
}
@Test
void getValueType() throws Exception {
public void testGetValueType() throws Exception {
LiteralExpression lEx = new LiteralExpression("somevalue");
assertThat(lEx.getValueType()).isEqualTo(String.class);
assertThat(lEx.getValueType(new StandardEvaluationContext())).isEqualTo(String.class);
assertThat(lEx.getValueType(new Rooty())).isEqualTo(String.class);
assertThat(lEx.getValueType(new StandardEvaluationContext(), new Rooty())).isEqualTo(String.class);
}
@Test
void getValueTypeDescriptor() throws Exception {
assertThat(lEx.getValueTypeDescriptor().getType()).isEqualTo(String.class);
assertThat(lEx.getValueTypeDescriptor(new StandardEvaluationContext()).getType()).isEqualTo(String.class);
assertThat(lEx.getValueTypeDescriptor(new Rooty()).getType()).isEqualTo(String.class);
assertThat(lEx.getValueTypeDescriptor(new StandardEvaluationContext(), new Rooty()).getType()).isEqualTo(String.class);
}
static class Rooty {}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +16,6 @@
package org.springframework.expression.spel;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.expression.spel.standard.SpelExpression;
@@ -26,530 +24,415 @@ 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.
*
* <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.
* 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.
*
* @author Andy Clement
* @author Sam Brannen
*/
class ParsingTests {
public class ParsingTests {
private final SpelExpressionParser parser = new SpelExpressionParser();
private SpelExpressionParser parser = new SpelExpressionParser();
@Nested
class Miscellaneous {
@Test
void literalNull() {
parseCheck("null");
}
@Test
void literalDate01() {
parseCheck("date('1974/08/24')");
}
@Test
void literalDate02() {
parseCheck("date('19740824T131030','yyyyMMddTHHmmss')");
}
@Test
void mixedOperators() {
parseCheck("true and 5>3", "(true and (5 > 3))");
}
@Test
void assignmentToVariables() {
parseCheck("#var1='value1'");
}
@Test
void collectionProcessorsCountStringArray() {
parseCheck("new String[] {'abc','def','xyz'}.count()");
}
@Test
void collectionProcessorsCountIntArray() {
parseCheck("new int[] {1,2,3}.count()");
}
@Test
void collectionProcessorsMax() {
parseCheck("new int[] {1,2,3}.max()");
}
@Test
void collectionProcessorsMin() {
parseCheck("new int[] {1,2,3}.min()");
}
@Test
void collectionProcessorsAverage() {
parseCheck("new int[] {1,2,3}.average()");
}
@Test
void collectionProcessorsSort() {
parseCheck("new int[] {3,2,1}.sort()");
}
@Test
void collectionProcessorsNonNull() {
parseCheck("{'a','b',null,'d',null}.nonNull()");
}
@Test
void collectionProcessorsDistinct() {
parseCheck("{'a','b','a','d','e'}.distinct()");
}
@Disabled("Unsupported syntax/feature")
@Test
void lambdaMax() {
parseCheck("(#max = {|x,y| $x > $y ? $x : $y }; #max(5,25))",
"(#max={|x,y| ($x > $y) ? $x : $y };#max(5,25))");
}
@Disabled("Unsupported syntax/feature")
@Test
void lambdaFactorial() {
parseCheck("(#fact = {|n| $n <= 1 ? 1 : $n * #fact($n-1) }; #fact(5))",
"(#fact={|n| ($n <= 1) ? 1 : ($n * #fact(($n - 1))) };#fact(5))");
}
@Disabled("Unsupported syntax/feature")
@Test
void projection() {
parseCheck("{1,2,3,4,5,6,7,8,9,10}.!{#isEven()}");
}
@Disabled("Unsupported syntax/feature")
@Test
void selection() {
parseCheck("{1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'}",
"{1,2,3,4,5,6,7,8,9,10}.?{(#isEven(#this) == 'y')}");
}
@Disabled("Unsupported syntax/feature")
@Test
void selectionFirst() {
parseCheck("{1,2,3,4,5,6,7,8,9,10}.^{#isEven(#this) == 'y'}",
"{1,2,3,4,5,6,7,8,9,10}.^{(#isEven(#this) == 'y')}");
}
@Disabled("Unsupported syntax/feature")
@Test
void selectionLast() {
parseCheck("{1,2,3,4,5,6,7,8,9,10}.${#isEven(#this) == 'y'}",
"{1,2,3,4,5,6,7,8,9,10}.${(#isEven(#this) == 'y')}");
}
// literals
@Test
public void testLiteralBoolean01() {
parseCheck("false");
}
@Nested
class LiteralBooleans {
@Test
void literalBooleanFalse() {
parseCheck("false");
}
@Test
void literalBooleanTrue() {
parseCheck("true");
}
@Test
void literalBooleanNotTrue() {
parseCheck("!true");
parseCheck("not true", "!true");
}
@Test
public void testLiteralLong01() {
parseCheck("37L", "37");
}
@Nested
class LiteralNumbers {
@Test
void literalLong() {
parseCheck("37L", "37");
}
@Test
void literalIntegers() {
parseCheck("1");
parseCheck("1415");
}
@Test
void literalReal() {
parseCheck("6.0221415E+23", "6.0221415E23");
}
@Test
void literalHex() {
parseCheck("0x7FFFFFFF", "2147483647");
}
@Test
public void testLiteralBoolean02() {
parseCheck("true");
}
@Nested
class LiteralStrings {
@Test
void insideSingleQuotes() {
parseCheck("'hello'");
parseCheck("'hello world'");
}
@Test
void insideDoubleQuotes() {
parseCheck("\"hello\"", "'hello'");
parseCheck("\"hello world\"", "'hello world'");
}
@Test
void singleQuotesInsideSingleQuotes() {
parseCheck("'Tony''s Pizza'");
parseCheck("'big ''''pizza'''' parlor'");
}
@Test
void doubleQuotesInsideDoubleQuotes() {
parseCheck("\"big \"\"pizza\"\" parlor\"", "'big \"pizza\" parlor'");
parseCheck("\"big \"\"\"\"pizza\"\"\"\" parlor\"", "'big \"\"pizza\"\" parlor'");
}
@Test
void singleQuotesInsideDoubleQuotes() {
parseCheck("\"Tony's Pizza\"", "'Tony''s Pizza'");
parseCheck("\"big ''pizza'' parlor\"", "'big ''''pizza'''' parlor'");
}
@Test
void doubleQuotesInsideSingleQuotes() {
parseCheck("'big \"pizza\" parlor'");
parseCheck("'two double \"\" quotes'");
}
@Test
void inCompoundExpressions() {
parseCheck("'123''4' == '123''4'", "('123''4' == '123''4')");
parseCheck("('123''4'=='123''4')", "('123''4' == '123''4')");
parseCheck("\"123\"\"4\" == \"123\"\"4\"", "('123\"4' == '123\"4')");
}
@Test
public void testLiteralBoolean03() {
parseCheck("!true");
}
@Nested
class BooleanOperators {
@Test
void booleanOperatorsOr01() {
parseCheck("false or false", "(false or false)");
}
@Test
void booleanOperatorsOr02() {
parseCheck("false or true", "(false or true)");
}
@Test
void booleanOperatorsOr03() {
parseCheck("true or false", "(true or false)");
}
@Test
void booleanOperatorsOr04() {
parseCheck("true or false", "(true or false)");
}
@Test
void booleanOperatorsMix() {
parseCheck("false or true and false", "(false or (true and false))");
}
@Test
public void testLiteralInteger01() {
parseCheck("1");
}
@Nested
class RelationalOperators {
@Test
void relOperatorsGT() {
parseCheck("3>6", "(3 > 6)");
}
@Test
void relOperatorsLT() {
parseCheck("3<6", "(3 < 6)");
}
@Test
void relOperatorsLE() {
parseCheck("3<=6", "(3 <= 6)");
}
@Test
void relOperatorsGE01() {
parseCheck("3>=6", "(3 >= 6)");
}
@Test
void relOperatorsGE02() {
parseCheck("3>=3", "(3 >= 3)");
}
@Disabled("Unsupported syntax/feature")
@Test
void relOperatorsIn() {
parseCheck("3 in {1,2,3,4,5}", "(3 in {1,2,3,4,5})");
}
@Test
void relOperatorsBetweenNumbers() {
parseCheck("1 between {1, 5}", "(1 between {1,5})");
}
@Test
void relOperatorsBetweenStrings() {
parseCheck("'efg' between {'abc', 'xyz'}", "('efg' between {'abc','xyz'})");
}
@Test
void relOperatorsInstanceOfInt() {
parseCheck("'xyz' instanceof int", "('xyz' instanceof int)");
}
@Test
void relOperatorsInstanceOfList() {
parseCheck("{1, 2, 3, 4, 5} instanceof List", "({1,2,3,4,5} instanceof List)");
}
@Test
void relOperatorsMatches() {
parseCheck("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "('5.0067' matches '^-?\\d+(\\.\\d{2})?$')");
parseCheck("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "('5.00' matches '^-?\\d+(\\.\\d{2})?$')");
}
@Test
public void testLiteralInteger02() {
parseCheck("1415");
}
@Nested
class MathematicalOperators {
@Test
void mathOperatorsAddIntegers() {
parseCheck("2+4", "(2 + 4)");
}
@Test
void mathOperatorsAddStrings() {
parseCheck("'a'+'b'", "('a' + 'b')");
}
@Test
void mathOperatorsAddMultipleStrings() {
parseCheck("'hello'+' '+'world'", "(('hello' + ' ') + 'world')");
}
@Test
void mathOperatorsSubtract() {
parseCheck("5-4", "(5 - 4)");
}
@Test
void mathOperatorsMultiply() {
parseCheck("7*4", "(7 * 4)");
}
@Test
void mathOperatorsDivide() {
parseCheck("8/4", "(8 / 4)");
}
@Test
void mathOperatorModulus() {
parseCheck("7 % 4", "(7 % 4)");
}
@Test
public void testLiteralString01() {
parseCheck("'hello'");
}
@Nested
class References {
@Test
void references() {
parseCheck("@foo");
parseCheck("@'foo.bar'");
parseCheck("@\"foo.bar.goo\"" , "@'foo.bar.goo'");
parseCheck("@$$foo");
}
@Test
public void testLiteralString02() {
parseCheck("'joe bloggs'");
}
@Nested
class Properties {
@Test
void propertiesSingle() {
parseCheck("name");
}
@Test
void propertiesDouble() {
parseCheck("placeofbirth.CitY");
}
@Test
void propertiesMultiple() {
parseCheck("a.b.c.d.e");
}
@Test
public void testLiteralString03() {
parseCheck("'Tony''s Pizza'", "'Tony's Pizza'");
}
@Nested
class InlineCollections {
@Test
void inlineListOfIntegers() {
parseCheck("{1,2,3,4}");
parseCheck("{1, 2, 3, 4, 5}", "{1,2,3,4,5}");
}
@Test
void inlineListOfStrings() {
parseCheck("{'abc','xyz'}", "{'abc','xyz'}");
parseCheck("{\"abc\", 'xyz'}", "{'abc','xyz'}");
}
@Test
void inlineMapStringToObject() {
parseCheck("{'key1':'Value 1','today':DateTime.Today}");
}
@Test
void inlineMapIntegerToString() {
parseCheck("{1:'January',2:'February',3:'March'}");
}
@Test
public void testLiteralReal01() {
parseCheck("6.0221415E+23", "6.0221415E23");
}
@Nested
class MethodsConstructorsAndArrays {
@Test
void methods() {
parseCheck("echo()");
parseCheck("echo(12)");
parseCheck("echo(name)");
parseCheck("echo('Jane')");
parseCheck("echo('Jane',32)");
parseCheck("echo('Jane', 32)", "echo('Jane',32)");
parseCheck("age.doubleItAndAdd(12)");
}
@Test
void constructorWithNoArguments() {
parseCheck("new Foo()");
parseCheck("new example.Foo()");
}
@Test
void constructorWithOneArgument() {
parseCheck("new String('hello')");
parseCheck("new String( 'hello' )", "new String('hello')");
parseCheck("new String(\"hello\" )", "new String('hello')");
}
@Test
void constructorWithMultipleArguments() {
parseCheck("new example.Person('Jane',32,true)");
parseCheck("new example.Person('Jane', 32, true)", "new example.Person('Jane',32,true)");
parseCheck("new example.Person('Jane', 2 * 16, true)", "new example.Person('Jane',(2 * 16),true)");
}
@Test
void arrayConstructionWithOneDimensionalReferenceType() {
parseCheck("new String[3]");
}
@Test
void arrayConstructionWithOneDimensionalFullyQualifiedReferenceType() {
parseCheck("new java.lang.String[3]");
}
@Test
void arrayConstructionWithOneDimensionalPrimitiveType() {
parseCheck("new int[3]");
}
@Test
void arrayConstructionWithMultiDimensionalReferenceType() {
parseCheck("new Float[3][4]");
}
@Test
void arrayConstructionWithMultiDimensionalPrimitiveType() {
parseCheck("new int[3][4]");
}
@Test
void arrayConstructionWithOneDimensionalReferenceTypeWithInitializer() {
parseCheck("new String[] {'abc','xyz'}");
parseCheck("new String[] {'abc', 'xyz'}", "new String[] {'abc','xyz'}");
}
@Test
void arrayConstructionWithOneDimensionalPrimitiveTypeWithInitializer() {
parseCheck("new int[] {1,2,3,4,5}");
parseCheck("new int[] {1, 2, 3, 4, 5}", "new int[] {1,2,3,4,5}");
}
@Test
public void testLiteralHex01() {
parseCheck("0x7FFFFFFF", "2147483647");
}
@Nested
class VariablesAndFunctions {
@Test
void variables() {
parseCheck("#foo");
}
@Test
void functions() {
parseCheck("#fn(1,2,3)");
parseCheck("#fn('hello')");
}
@Test
public void testLiteralDate01() {
parseCheck("date('1974/08/24')");
}
@Nested
class ElvisAndTernaryOperators {
@Test
void elvis() {
parseCheck("3?:1", "(3 ?: 1)");
parseCheck("(2*3)?:1*10", "((2 * 3) ?: (1 * 10))");
parseCheck("((2*3)?:1)*10", "(((2 * 3) ?: 1) * 10)");
}
@Test
void ternary() {
parseCheck("1>2?3:4", "((1 > 2) ? 3 : 4)");
parseCheck("(a ? 1 : 0) * 10", "((a ? 1 : 0) * 10)");
parseCheck("(a?1:0)*10", "((a ? 1 : 0) * 10)");
parseCheck("(4 % 2 == 0 ? 1 : 0) * 10", "((((4 % 2) == 0) ? 1 : 0) * 10)");
parseCheck("((4 % 2 == 0) ? 1 : 0) * 10", "((((4 % 2) == 0) ? 1 : 0) * 10)");
parseCheck("{1}.#isEven(#this) == 'y'?'it is even':'it is odd'",
"(({1}.#isEven(#this) == 'y') ? 'it is even' : 'it is odd')");
}
@Test
public void testLiteralDate02() {
parseCheck("date('19740824T131030','yyyyMMddTHHmmss')");
}
@Nested
class TypeReferences {
@Test
void typeReferences01() {
parseCheck("T(java.lang.String)");
}
@Test
void typeReferences02() {
parseCheck("T(String)");
}
@Test
public void testLiteralNull01() {
parseCheck("null");
}
// boolean operators
@Test
public void testBooleanOperatorsOr01() {
parseCheck("false or false", "(false or false)");
}
@Test
public void testBooleanOperatorsOr02() {
parseCheck("false or true", "(false or true)");
}
@Test
public void testBooleanOperatorsOr03() {
parseCheck("true or false", "(true or false)");
}
@Test
public void testBooleanOperatorsOr04() {
parseCheck("true or false", "(true or false)");
}
@Test
public void testBooleanOperatorsMix01() {
parseCheck("false or true and false", "(false or (true and false))");
}
// relational operators
@Test
public void testRelOperatorsGT01() {
parseCheck("3>6", "(3 > 6)");
}
@Test
public void testRelOperatorsLT01() {
parseCheck("3<6", "(3 < 6)");
}
@Test
public void testRelOperatorsLE01() {
parseCheck("3<=6", "(3 <= 6)");
}
@Test
public void testRelOperatorsGE01() {
parseCheck("3>=6", "(3 >= 6)");
}
@Test
public void testRelOperatorsGE02() {
parseCheck("3>=3", "(3 >= 3)");
}
@Test
public void testElvis() {
parseCheck("3?:1", "3 ?: 1");
}
// public void testRelOperatorsIn01() {
// parseCheck("3 in {1,2,3,4,5}", "(3 in {1,2,3,4,5})");
// }
//
// public void testRelOperatorsBetween01() {
// parseCheck("1 between {1, 5}", "(1 between {1,5})");
// }
// public void testRelOperatorsBetween02() {
// parseCheck("'efg' between {'abc', 'xyz'}", "('efg' between {'abc','xyz'})");
// }// true
@Test
public void testRelOperatorsIs01() {
parseCheck("'xyz' instanceof int", "('xyz' instanceof int)");
}// false
// public void testRelOperatorsIs02() {
// parseCheck("{1, 2, 3, 4, 5} instanceof List", "({1,2,3,4,5} instanceof List)");
// }// true
@Test
public void testRelOperatorsMatches01() {
parseCheck("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "('5.0067' matches '^-?\\d+(\\.\\d{2})?$')");
}// false
@Test
public void testRelOperatorsMatches02() {
parseCheck("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "('5.00' matches '^-?\\d+(\\.\\d{2})?$')");
}// true
// mathematical operators
@Test
public void testMathOperatorsAdd01() {
parseCheck("2+4", "(2 + 4)");
}
@Test
public void testMathOperatorsAdd02() {
parseCheck("'a'+'b'", "('a' + 'b')");
}
@Test
public void testMathOperatorsAdd03() {
parseCheck("'hello'+' '+'world'", "(('hello' + ' ') + 'world')");
}
@Test
public void testMathOperatorsSubtract01() {
parseCheck("5-4", "(5 - 4)");
}
@Test
public void testMathOperatorsMultiply01() {
parseCheck("7*4", "(7 * 4)");
}
@Test
public void testMathOperatorsDivide01() {
parseCheck("8/4", "(8 / 4)");
}
@Test
public void testMathOperatorModulus01() {
parseCheck("7 % 4", "(7 % 4)");
}
// mixed operators
@Test
public void testMixedOperators01() {
parseCheck("true and 5>3", "(true and (5 > 3))");
}
// collection processors
// public void testCollectionProcessorsCount01() {
// parseCheck("new String[] {'abc','def','xyz'}.count()");
// }
// public void testCollectionProcessorsCount02() {
// parseCheck("new int[] {1,2,3}.count()");
// }
//
// public void testCollectionProcessorsMax01() {
// parseCheck("new int[] {1,2,3}.max()");
// }
//
// public void testCollectionProcessorsMin01() {
// parseCheck("new int[] {1,2,3}.min()");
// }
//
// public void testCollectionProcessorsAverage01() {
// parseCheck("new int[] {1,2,3}.average()");
// }
//
// public void testCollectionProcessorsSort01() {
// parseCheck("new int[] {3,2,1}.sort()");
// }
//
// public void testCollectionProcessorsNonNull01() {
// parseCheck("{'a','b',null,'d',null}.nonNull()");
// }
//
// public void testCollectionProcessorsDistinct01() {
// parseCheck("{'a','b','a','d','e'}.distinct()");
// }
// references
@Test
public void testReferences01() {
parseCheck("@foo");
parseCheck("@'foo.bar'");
parseCheck("@\"foo.bar.goo\"","@'foo.bar.goo'");
}
@Test
public void testReferences03() {
parseCheck("@$$foo");
}
// properties
@Test
public void testProperties01() {
parseCheck("name");
}
@Test
public void testProperties02() {
parseCheck("placeofbirth.CitY");
}
@Test
public void testProperties03() {
parseCheck("a.b.c.d.e");
}
// inline list creation
@Test
public void testInlineListCreation01() {
parseCheck("{1, 2, 3, 4, 5}", "{1,2,3,4,5}");
}
@Test
public void testInlineListCreation02() {
parseCheck("{'abc','xyz'}", "{'abc','xyz'}");
}
// inline map creation
@Test
public void testInlineMapCreation01() {
parseCheck("{'key1':'Value 1','today':DateTime.Today}");
}
@Test
public void testInlineMapCreation02() {
parseCheck("{1:'January',2:'February',3:'March'}");
}
// methods
@Test
public void testMethods01() {
parseCheck("echo(12)");
}
@Test
public void testMethods02() {
parseCheck("echo(name)");
}
@Test
public void testMethods03() {
parseCheck("age.doubleItAndAdd(12)");
}
// constructors
@Test
public void testConstructors01() {
parseCheck("new String('hello')");
}
// public void testConstructors02() {
// parseCheck("new String[3]");
// }
// array construction
// public void testArrayConstruction01() {
// parseCheck("new int[] {1, 2, 3, 4, 5}", "new int[] {1,2,3,4,5}");
// }
//
// public void testArrayConstruction02() {
// parseCheck("new String[] {'abc','xyz'}", "new String[] {'abc','xyz'}");
// }
// variables and functions
@Test
public void testVariables01() {
parseCheck("#foo");
}
@Test
public void testFunctions01() {
parseCheck("#fn(1,2,3)");
}
@Test
public void testFunctions02() {
parseCheck("#fn('hello')");
}
// projections and selections
// public void testProjections01() {
// parseCheck("{1,2,3,4,5,6,7,8,9,10}.!{#isEven()}");
// }
// public void testSelections01() {
// 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() {
// 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() {
// 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() {
parseCheck("#var1='value1'");
}
// ternary operator
@Test
public void testTernaryOperator01() {
parseCheck("1>2?3:4","(1 > 2) ? 3 : 4");
}
// public void testTernaryOperator01() {
// parseCheck("{1}.#isEven(#this) == 'y'?'it is even':'it is odd'",
// "({1}.#isEven(#this) == 'y') ? 'it is even' : 'it is odd'");
// }
//
// public void testLambdaMax() {
// parseCheck("(#max = {|x,y| $x > $y ? $x : $y }; #max(5,25))", "(#max={|x,y| ($x > $y) ? $x : $y };#max(5,25))");
// }
//
// public void testLambdaFactorial() {
// 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() {
parseCheck("T(java.lang.String)");
}
@Test
public void testTypeReferences02() {
parseCheck("T(String)");
}
@Test
public void testInlineList1() {
parseCheck("{1,2,3,4}");
}
/**
* Parse the supplied expression and then create a string representation of the resultant AST, it should be the same
@@ -557,7 +440,7 @@ class ParsingTests {
*
* @param expression the expression to parse *and* the expected value of the string form of the resultant AST
*/
private void parseCheck(String expression) {
public void parseCheck(String expression) {
parseCheck(expression, expression);
}
@@ -568,7 +451,7 @@ class ParsingTests {
* @param expression the expression to parse
* @param expectedStringFormOfAST the expected string form of the AST
*/
private void parseCheck(String expression, String expectedStringFormOfAST) {
public void parseCheck(String expression, String expectedStringFormOfAST) {
SpelExpression e = parser.parseRaw(expression);
assertThat(e).isNotNull();
assertThat(e.toStringAST()).isEqualTo(expectedStringFormOfAST);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -209,7 +209,7 @@ class SpelReproTests extends AbstractExpressionTests {
checkTemplateParsingError("abc${ } }", "No expression defined within delimiter '${}' at character 3");
checkTemplateParsingError("abc$[ } ]", DOLLARSQUARE_TEMPLATE_PARSER_CONTEXT, "Found closing '}' at position 6 without an opening '{'");
checkTemplateParsing("abc ${\"def''g}hi\"} jkl", "abc def''g}hi jkl");
checkTemplateParsing("abc ${\"def''g}hi\"} jkl", "abc def'g}hi jkl");
checkTemplateParsing("abc ${'def''g}hi'} jkl", "abc def'g}hi jkl");
checkTemplateParsing("}", "}");
checkTemplateParsing("${'hello'} world", "hello world");
@@ -1,34 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel;
import org.junit.platform.suite.api.IncludeClassNamePatterns;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
/**
* JUnit Platform based test suite for tests in the spring-expression module.
*
* <p><strong>This suite is only intended to be used manually within an IDE.</strong>
*
* @author Sam Brannen
*/
@Suite
@SelectPackages("org.springframework.expression.spel")
@IncludeClassNamePatterns(".*Tests?$")
class SpringExpressionTestSuite {
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 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 {@link ParsedSql} instance
* @return the parsed statement, represented as ParsedSql instance
*/
public static ParsedSql parseSqlStatement(String sql) {
public static ParsedSql parseSqlStatement(final String sql) {
Assert.notNull(sql, "SQL must not be null");
Set<String> namedParameters = new HashSet<>();
@@ -122,20 +122,17 @@ 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);
}
@@ -147,8 +144,7 @@ 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);
}
@@ -189,8 +185,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++;
@@ -275,7 +271,6 @@ 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++) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,15 +30,15 @@ import org.springframework.lang.Nullable;
/**
* Object to represent an SQL BLOB/CLOB value parameter. BLOBs can either be an
* InputStream or a byte array. CLOBs can be in the form of a Reader, InputStream,
* InputStream or a byte array. CLOBs can be in the form of a Reader, InputStream
* or String. Each CLOB/BLOB value will be stored together with its length.
* The type is based on which constructor is used. Instances of this class are
* stateful and immutable: use them and discard them.
* The type is based on which constructor is used. Objects of this class are
* immutable except for the LobCreator reference. Use them and discard them.
*
* <p>This class holds a reference to a {@link LobCreator} that must be closed after
* the update has completed. This is done via a call to the {@link #cleanup()} method.
* All handling of the {@code LobCreator} is done by the framework classes that use it -
* no need to set or close the {@code LobCreator} for end users of this class.
* <p>This class holds a reference to a LocCreator that must be closed after the
* update has completed. This is done via a call to the closeLobCreator method.
* All handling of the LobCreator is done by the framework classes that use it -
* no need to set or close the LobCreator for end users of this class.
*
* <p>A usage example:
*
@@ -72,7 +72,8 @@ public class SqlLobValue implements DisposableSqlTypeValue {
private final int length;
/**
* Reference to the LobCreator - so we can close it once the update is done.
* This contains a reference to the LobCreator - so we can close it
* once the update is done.
*/
private final LobCreator lobCreator;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -69,10 +69,10 @@ public class SQLExceptionSubclassTranslator extends AbstractFallbackSQLException
if (ex instanceof SQLTransientConnectionException) {
return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex);
}
if (ex instanceof SQLTransactionRollbackException) {
else if (ex instanceof SQLTransactionRollbackException) {
return new ConcurrencyFailureException(buildMessage(task, sql, ex), ex);
}
if (ex instanceof SQLTimeoutException) {
else if (ex instanceof SQLTimeoutException) {
return new QueryTimeoutException(buildMessage(task, sql, ex), ex);
}
}
@@ -80,19 +80,19 @@ public class SQLExceptionSubclassTranslator extends AbstractFallbackSQLException
if (ex instanceof SQLNonTransientConnectionException) {
return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex);
}
if (ex instanceof SQLDataException) {
else if (ex instanceof SQLDataException) {
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
}
if (ex instanceof SQLIntegrityConstraintViolationException) {
else if (ex instanceof SQLIntegrityConstraintViolationException) {
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
}
if (ex instanceof SQLInvalidAuthorizationSpecException) {
else if (ex instanceof SQLInvalidAuthorizationSpecException) {
return new PermissionDeniedDataAccessException(buildMessage(task, sql, ex), ex);
}
if (ex instanceof SQLSyntaxErrorException) {
else if (ex instanceof SQLSyntaxErrorException) {
return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex);
}
if (ex instanceof SQLFeatureNotSupportedException) {
else if (ex instanceof SQLFeatureNotSupportedException) {
return new InvalidDataAccessApiUsageException(buildMessage(task, sql, ex), ex);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 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,29 +319,4 @@ 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"));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 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,6 @@
package org.springframework.jdbc.support;
import java.sql.SQLDataException;
import java.sql.SQLException;
import org.junit.jupiter.api.Test;
@@ -38,8 +37,8 @@ public class SQLExceptionCustomTranslatorTests {
private static SQLErrorCodes ERROR_CODES = new SQLErrorCodes();
static {
ERROR_CODES.setBadSqlGrammarCodes("1");
ERROR_CODES.setDataAccessResourceFailureCodes("2");
ERROR_CODES.setBadSqlGrammarCodes(new String[] { "1" });
ERROR_CODES.setDataAccessResourceFailureCodes(new String[] { "2" });
ERROR_CODES.setCustomSqlExceptionTranslatorClass(CustomSqlExceptionTranslator.class);
}
@@ -48,7 +47,7 @@ public class SQLExceptionCustomTranslatorTests {
@Test
public void badSqlGrammarException() {
SQLException badSqlGrammarExceptionEx = new SQLDataException("", "", 1);
SQLException badSqlGrammarExceptionEx = SQLExceptionSubclassFactory.newSQLDataException("", "", 1);
DataAccessException dae = sext.translate("task", "SQL", badSqlGrammarExceptionEx);
assertThat(dae.getCause()).isEqualTo(badSqlGrammarExceptionEx);
assertThat(dae).isInstanceOf(BadSqlGrammarException.class);
@@ -56,7 +55,7 @@ public class SQLExceptionCustomTranslatorTests {
@Test
public void dataAccessResourceException() {
SQLException dataAccessResourceEx = new SQLDataException("", "", 2);
SQLException dataAccessResourceEx = SQLExceptionSubclassFactory.newSQLDataException("", "", 2);
DataAccessException dae = sext.translate("task", "SQL", dataAccessResourceEx);
assertThat(dae.getCause()).isEqualTo(dataAccessResourceEx);
assertThat(dae).isInstanceOf(TransientDataAccessResourceException.class);
@@ -0,0 +1,78 @@
/*
* 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.jdbc.support;
import java.sql.SQLDataException;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.sql.SQLInvalidAuthorizationSpecException;
import java.sql.SQLNonTransientConnectionException;
import java.sql.SQLRecoverableException;
import java.sql.SQLSyntaxErrorException;
import java.sql.SQLTimeoutException;
import java.sql.SQLTransactionRollbackException;
import java.sql.SQLTransientConnectionException;
/**
* Class to generate {@link SQLException} subclasses for testing purposes.
*
* @author Thomas Risberg
*/
public class SQLExceptionSubclassFactory {
public static SQLException newSQLDataException(String reason, String SQLState, int vendorCode) {
return new SQLDataException(reason, SQLState, vendorCode);
}
public static SQLException newSQLFeatureNotSupportedException(String reason, String SQLState, int vendorCode) {
return new SQLFeatureNotSupportedException(reason, SQLState, vendorCode);
}
public static SQLException newSQLIntegrityConstraintViolationException(String reason, String SQLState, int vendorCode) {
return new SQLIntegrityConstraintViolationException(reason, SQLState, vendorCode);
}
public static SQLException newSQLInvalidAuthorizationSpecException(String reason, String SQLState, int vendorCode) {
return new SQLInvalidAuthorizationSpecException(reason, SQLState, vendorCode);
}
public static SQLException newSQLNonTransientConnectionException(String reason, String SQLState, int vendorCode) {
return new SQLNonTransientConnectionException(reason, SQLState, vendorCode);
}
public static SQLException newSQLSyntaxErrorException(String reason, String SQLState, int vendorCode) {
return new SQLSyntaxErrorException(reason, SQLState, vendorCode);
}
public static SQLException newSQLTransactionRollbackException(String reason, String SQLState, int vendorCode) {
return new SQLTransactionRollbackException(reason, SQLState, vendorCode);
}
public static SQLException newSQLTransientConnectionException(String reason, String SQLState, int vendorCode) {
return new SQLTransientConnectionException(reason, SQLState, vendorCode);
}
public static SQLException newSQLTimeoutException(String reason, String SQLState, int vendorCode) {
return new SQLTimeoutException(reason, SQLState, vendorCode);
}
public static SQLException newSQLRecoverableException(String reason, String SQLState, int vendorCode) {
return new SQLRecoverableException(reason, SQLState, vendorCode);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 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,22 +16,11 @@
package org.springframework.jdbc.support;
import java.sql.SQLDataException;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.sql.SQLInvalidAuthorizationSpecException;
import java.sql.SQLNonTransientConnectionException;
import java.sql.SQLRecoverableException;
import java.sql.SQLSyntaxErrorException;
import java.sql.SQLTimeoutException;
import java.sql.SQLTransactionRollbackException;
import java.sql.SQLTransientConnectionException;
import org.junit.jupiter.api.Test;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
@@ -45,41 +34,78 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Thomas Risberg
* @author Juergen Hoeller
*/
public class SQLExceptionSubclassTranslatorTests {
@Test
public void exceptionClassTranslation() {
doTest(new SQLDataException("", "", 0), DataIntegrityViolationException.class);
doTest(new SQLFeatureNotSupportedException("", "", 0), InvalidDataAccessApiUsageException.class);
doTest(new SQLIntegrityConstraintViolationException("", "", 0), DataIntegrityViolationException.class);
doTest(new SQLInvalidAuthorizationSpecException("", "", 0), PermissionDeniedDataAccessException.class);
doTest(new SQLNonTransientConnectionException("", "", 0), DataAccessResourceFailureException.class);
doTest(new SQLRecoverableException("", "", 0), RecoverableDataAccessException.class);
doTest(new SQLSyntaxErrorException("", "", 0), BadSqlGrammarException.class);
doTest(new SQLTimeoutException("", "", 0), QueryTimeoutException.class);
doTest(new SQLTransactionRollbackException("", "", 0), ConcurrencyFailureException.class);
doTest(new SQLTransientConnectionException("", "", 0), TransientDataAccessResourceException.class);
private static SQLErrorCodes ERROR_CODES = new SQLErrorCodes();
static {
ERROR_CODES.setBadSqlGrammarCodes("1");
}
@Test
public void fallbackStateTranslation() {
public void errorCodeTranslation() {
SQLExceptionTranslator sext = new SQLErrorCodeSQLExceptionTranslator(ERROR_CODES);
SQLException dataIntegrityViolationEx = SQLExceptionSubclassFactory.newSQLDataException("", "", 0);
DataIntegrityViolationException divex = (DataIntegrityViolationException) sext.translate("task", "SQL", dataIntegrityViolationEx);
assertThat(divex.getCause()).isEqualTo(dataIntegrityViolationEx);
SQLException featureNotSupEx = SQLExceptionSubclassFactory.newSQLFeatureNotSupportedException("", "", 0);
InvalidDataAccessApiUsageException idaex = (InvalidDataAccessApiUsageException) sext.translate("task", "SQL", featureNotSupEx);
assertThat(idaex.getCause()).isEqualTo(featureNotSupEx);
SQLException dataIntegrityViolationEx2 = SQLExceptionSubclassFactory.newSQLIntegrityConstraintViolationException("", "", 0);
DataIntegrityViolationException divex2 = (DataIntegrityViolationException) sext.translate("task", "SQL", dataIntegrityViolationEx2);
assertThat(divex2.getCause()).isEqualTo(dataIntegrityViolationEx2);
SQLException permissionDeniedEx = SQLExceptionSubclassFactory.newSQLInvalidAuthorizationSpecException("", "", 0);
PermissionDeniedDataAccessException pdaex = (PermissionDeniedDataAccessException) sext.translate("task", "SQL", permissionDeniedEx);
assertThat(pdaex.getCause()).isEqualTo(permissionDeniedEx);
SQLException dataAccessResourceEx = SQLExceptionSubclassFactory.newSQLNonTransientConnectionException("", "", 0);
DataAccessResourceFailureException darex = (DataAccessResourceFailureException) sext.translate("task", "SQL", dataAccessResourceEx);
assertThat(darex.getCause()).isEqualTo(dataAccessResourceEx);
SQLException badSqlEx2 = SQLExceptionSubclassFactory.newSQLSyntaxErrorException("", "", 0);
BadSqlGrammarException bsgex2 = (BadSqlGrammarException) sext.translate("task", "SQL2", badSqlEx2);
assertThat(bsgex2.getSql()).isEqualTo("SQL2");
assertThat((Object) bsgex2.getSQLException()).isEqualTo(badSqlEx2);
SQLException tranRollbackEx = SQLExceptionSubclassFactory.newSQLTransactionRollbackException("", "", 0);
ConcurrencyFailureException cfex = (ConcurrencyFailureException) sext.translate("task", "SQL", tranRollbackEx);
assertThat(cfex.getCause()).isEqualTo(tranRollbackEx);
SQLException transientConnEx = SQLExceptionSubclassFactory.newSQLTransientConnectionException("", "", 0);
TransientDataAccessResourceException tdarex = (TransientDataAccessResourceException) sext.translate("task", "SQL", transientConnEx);
assertThat(tdarex.getCause()).isEqualTo(transientConnEx);
SQLException transientConnEx2 = SQLExceptionSubclassFactory.newSQLTimeoutException("", "", 0);
QueryTimeoutException tdarex2 = (QueryTimeoutException) sext.translate("task", "SQL", transientConnEx2);
assertThat(tdarex2.getCause()).isEqualTo(transientConnEx2);
SQLException recoverableEx = SQLExceptionSubclassFactory.newSQLRecoverableException("", "", 0);
RecoverableDataAccessException rdaex2 = (RecoverableDataAccessException) sext.translate("task", "SQL", recoverableEx);
assertThat(rdaex2.getCause()).isEqualTo(recoverableEx);
// Test classic error code translation. We should move there next if the exception we pass in is not one
// of the new subclasses.
SQLException sexEct = new SQLException("", "", 1);
BadSqlGrammarException bsgEct = (BadSqlGrammarException) sext.translate("task", "SQL-ECT", sexEct);
assertThat(bsgEct.getSql()).isEqualTo("SQL-ECT");
assertThat((Object) bsgEct.getSQLException()).isEqualTo(sexEct);
// Test fallback. We assume that no database will ever return this error code,
// but 07xxx will be bad grammar picked up by the fallback SQLState translator
doTest(new SQLException("", "07xxx", 666666666), BadSqlGrammarException.class);
SQLException sexFbt = new SQLException("", "07xxx", 666666666);
BadSqlGrammarException bsgFbt = (BadSqlGrammarException) sext.translate("task", "SQL-FBT", sexFbt);
assertThat(bsgFbt.getSql()).isEqualTo("SQL-FBT");
assertThat((Object) bsgFbt.getSQLException()).isEqualTo(sexFbt);
// and 08xxx will be data resource failure (non-transient) picked up by the fallback SQLState translator
doTest(new SQLException("", "08xxx", 666666666), DataAccessResourceFailureException.class);
}
private void doTest(SQLException ex, Class<?> dataAccessExceptionType) {
SQLExceptionTranslator translator = new SQLExceptionSubclassTranslator();
DataAccessException dax = translator.translate("task", "SQL", ex);
assertThat(dax).as("Specific translation must not result in null").isNotNull();
assertThat(dax).as("Wrong DataAccessException type returned").isExactlyInstanceOf(dataAccessExceptionType);
assertThat(dax.getCause()).as("The exact same original SQLException must be preserved").isSameAs(ex);
SQLException sexFbt2 = new SQLException("", "08xxx", 666666666);
DataAccessResourceFailureException darfFbt = (DataAccessResourceFailureException) sext.translate("task", "SQL-FBT2", sexFbt2);
assertThat(darfFbt.getCause()).isEqualTo(sexFbt2);
}
}
@@ -0,0 +1,77 @@
/*
* Copyright 2002-2019 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.jdbc.support;
import java.sql.SQLException;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.BadSqlGrammarException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rod Johnson
* @since 13-Jan-03
*/
public class SQLStateExceptionTranslatorTests {
private static final String sql = "SELECT FOO FROM BAR";
private final SQLStateSQLExceptionTranslator trans = new SQLStateSQLExceptionTranslator();
// ALSO CHECK CHAIN of SQLExceptions!?
// also allow chain of translators? default if can't do specific?
@Test
public void badSqlGrammar() {
SQLException sex = new SQLException("Message", "42001", 1);
try {
throw this.trans.translate("task", sql, sex);
}
catch (BadSqlGrammarException ex) {
// OK
assertThat(sql.equals(ex.getSql())).as("SQL is correct").isTrue();
assertThat(sex.equals(ex.getSQLException())).as("Exception matches").isTrue();
}
}
@Test
public void invalidSqlStateCode() {
SQLException sex = new SQLException("Message", "NO SUCH CODE", 1);
assertThat(this.trans.translate("task", sql, sex)).isNull();
}
/**
* PostgreSQL can return null.
* SAP DB can apparently return empty SQL code.
* Bug 729170
*/
@Test
public void malformedSqlStateCodes() {
SQLException sex = new SQLException("Message", null, 1);
assertThat(this.trans.translate("task", sql, sex)).isNull();
sex = new SQLException("Message", "", 1);
assertThat(this.trans.translate("task", sql, sex)).isNull();
// One char's not allowed
sex = new SQLException("Message", "I", 1);
assertThat(this.trans.translate("task", sql, sex)).isNull();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,7 +26,6 @@ import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.TransientDataAccessResourceException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -38,73 +37,58 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*/
public class SQLStateSQLExceptionTranslatorTests {
private static final String REASON = "The game is afoot!";
private static final String TASK = "Counting sheep... yawn.";
private static final String SQL = "select count(0) from t_sheep where over_fence = ... yawn... 1";
@Test
public void translateNullException() {
public void testTranslateNullException() {
assertThatIllegalArgumentException().isThrownBy(() ->
new SQLStateSQLExceptionTranslator().translate("", "", null));
}
@Test
public void translateBadSqlGrammar() {
public void testTranslateBadSqlGrammar() {
doTest("07", BadSqlGrammarException.class);
}
@Test
public void translateDataIntegrityViolation() {
public void testTranslateDataIntegrityViolation() {
doTest("23", DataIntegrityViolationException.class);
}
@Test
public void translateDataAccessResourceFailure() {
public void testTranslateDataAccessResourceFailure() {
doTest("53", DataAccessResourceFailureException.class);
}
@Test
public void translateTransientDataAccessResourceFailure() {
public void testTranslateTransientDataAccessResourceFailure() {
doTest("S1", TransientDataAccessResourceException.class);
}
@Test
public void translateConcurrencyFailure() {
public void testTranslateConcurrencyFailure() {
doTest("40", ConcurrencyFailureException.class);
}
@Test
public void translateUncategorized() {
doTest("00000000", null);
}
@Test
public void invalidSqlStateCode() {
doTest("NO SUCH CODE", null);
}
/**
* PostgreSQL can return null.
* SAP DB can apparently return empty SQL code.
* Bug 729170
*/
@Test
public void malformedSqlStateCodes() {
doTest(null, null);
doTest("", null);
doTest("I", null);
public void testTranslateUncategorized() {
assertThat(new SQLStateSQLExceptionTranslator().translate("", "", new SQLException(REASON, "00000000"))).isNull();
}
private void doTest(@Nullable String sqlState, @Nullable Class<?> dataAccessExceptionType) {
private void doTest(String sqlState, Class<?> dataAccessExceptionType) {
SQLException ex = new SQLException(REASON, sqlState);
SQLExceptionTranslator translator = new SQLStateSQLExceptionTranslator();
SQLException ex = new SQLException("reason", sqlState);
DataAccessException dax = translator.translate("task", "SQL", ex);
if (dataAccessExceptionType == null) {
assertThat(dax).as("Expected translation to null").isNull();
return;
}
assertThat(dax).as("Specific translation must not result in null").isNotNull();
assertThat(dax).as("Wrong DataAccessException type returned").isExactlyInstanceOf(dataAccessExceptionType);
assertThat(dax.getCause()).as("The exact same original SQLException must be preserved").isSameAs(ex);
DataAccessException dax = translator.translate(TASK, SQL, ex);
assertThat(dax).as("Specific translation must not result in a null DataAccessException being returned.").isNotNull();
assertThat(dax.getClass()).as("Wrong DataAccessException type returned as the result of the translation").isEqualTo(dataAccessExceptionType);
assertThat(dax.getCause()).as("The original SQLException must be preserved in the translated DataAccessException").isNotNull();
assertThat(dax.getCause()).as("The exact same original SQLException must be preserved in the translated DataAccessException").isSameAs(ex);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors
* Copyright 2002-2021 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.jdbc.core
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import org.springframework.jdbc.core.test.ConstructorPerson
import java.math.BigDecimal
@@ -31,7 +31,7 @@ class KotlinDataClassRowMapperTests : AbstractRowMapperTests() {
"select name, age, birth_date, balance from people",
DataClassRowMapper(ConstructorPerson::class.java)
)
assertThat(result.size).isEqualTo(1)
Assertions.assertThat(result.size).isEqualTo(1)
verifyPerson(result[0])
mock.verifyClosed()
}
@@ -43,8 +43,8 @@ class KotlinDataClassRowMapperTests : AbstractRowMapperTests() {
"select name, age, birth_date, balance from people",
DataClassRowMapper(KotlinPerson::class.java)
)
assertThat(result.size).isEqualTo(1)
assertThat(result[0].name).isEqualTo("Bubba appended by init")
Assertions.assertThat(result.size).isEqualTo(1)
Assertions.assertThat(result[0].name).isEqualTo("Bubba appended by init")
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2018 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.
@@ -193,13 +193,7 @@ public abstract class JmsAccessor implements InitializingBean {
* @see javax.jms.ConnectionFactory#createConnection()
*/
protected Connection createConnection() throws JMSException {
ConnectionFactory cf = obtainConnectionFactory();
Connection con = cf.createConnection();
if (con == null) {
throw new javax.jms.IllegalStateException(
"ConnectionFactory returned null from createConnection(): " + cf);
}
return con;
return obtainConnectionFactory().createConnection();
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 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.
@@ -103,7 +103,7 @@ final class DefaultRSocketRequesterBuilder implements RSocketRequester.Builder {
@Override
public RSocketRequester.Builder metadataMimeType(MimeType mimeType) {
Assert.notNull(mimeType, "'metadataMimeType' is required");
Assert.notNull(mimeType, "`metadataMimeType` is required");
this.metadataMimeType = mimeType;
return this;
}
@@ -281,7 +281,7 @@ final class DefaultRSocketRequesterBuilder implements RSocketRequester.Builder {
Mono<DataBuffer> dataMono = Mono.empty();
if (data != null) {
ReactiveAdapter adapter = strategies.reactiveAdapterRegistry().getAdapter(data.getClass());
Assert.isTrue(adapter == null || !adapter.isMultiValue(), () -> "Expected single value: " + data);
Assert.isTrue(adapter == null || !adapter.isMultiValue(), "Expected single value: " + data);
Mono<?> mono = (adapter != null ? Mono.from(adapter.toPublisher(data)) : Mono.just(data));
dataMono = mono.map(value -> {
ResolvableType type = ResolvableType.forClass(value.getClass());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 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.
@@ -141,8 +141,7 @@ final class MetadataEncoder {
}
ReactiveAdapter adapter = this.strategies.reactiveAdapterRegistry().getAdapter(metadata.getClass());
if (adapter != null) {
Object originalMetadata = metadata;
Assert.isTrue(!adapter.isMultiValue(), () -> "Expected single value: " + originalMetadata);
Assert.isTrue(!adapter.isMultiValue(), "Expected single value: " + metadata);
metadata = Mono.from(adapter.toPublisher(metadata)).defaultIfEmpty(NO_VALUE);
this.hasAsyncValues = true;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 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.
@@ -421,7 +421,7 @@ public class RSocketMessageHandler extends MessageMappingMessageHandler {
String str = setupPayload.dataMimeType();
MimeType dataMimeType = StringUtils.hasText(str) ? MimeTypeUtils.parseMimeType(str) : this.defaultDataMimeType;
Assert.notNull(dataMimeType, "No `dataMimeType` in ConnectionSetupPayload and no default value");
Assert.isTrue(isDataMimeTypeSupported(dataMimeType), () -> "Data MimeType '" + dataMimeType + "' not supported");
Assert.isTrue(isDataMimeTypeSupported(dataMimeType), "Data MimeType '" + dataMimeType + "' not supported");
str = setupPayload.metadataMimeType();
MimeType metaMimeType = StringUtils.hasText(str) ? MimeTypeUtils.parseMimeType(str) : this.defaultMetadataMimeType;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -138,7 +138,7 @@ public class SimpMessagingTemplate extends AbstractMessageSendingTemplate<String
*/
@Override
public void send(Message<?> message) {
Assert.notNull(message, "Message must not be null");
Assert.notNull(message, "Message is required");
String destination = SimpMessageHeaderAccessor.getDestination(message.getHeaders());
if (destination != null) {
sendInternal(message);
@@ -224,8 +224,7 @@ public class SimpMessagingTemplate extends AbstractMessageSendingTemplate<String
throws MessagingException {
Assert.notNull(user, "User must not be null");
String username = user;
Assert.isTrue(!user.contains("%2F"), () -> "Invalid sequence \"%2F\" in user name: " + username);
Assert.isTrue(!user.contains("%2F"), "Invalid sequence \"%2F\" in user name: " + user);
user = StringUtils.replace(user, "/", "%2F");
destination = destination.startsWith("/") ? destination : "/" + destination;
super.convertAndSend(this.destinationPrefix + user + destination, payload, headers, postProcessor);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 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.
@@ -148,7 +148,7 @@ public class OrderedMessageChannelDecorator implements MessageChannel {
public static void configureInterceptor(MessageChannel channel, boolean preserveOrder) {
if (preserveOrder) {
Assert.isInstanceOf(ExecutorSubscribableChannel.class, channel,
"An ExecutorSubscribableChannel is required for 'preservePublishOrder'");
"An ExecutorSubscribableChannel is required for `preservePublishOrder`");
ExecutorSubscribableChannel execChannel = (ExecutorSubscribableChannel) channel;
if (execChannel.getInterceptors().stream().noneMatch(i -> i instanceof CallbackInterceptor)) {
execChannel.addInterceptor(0, new CallbackInterceptor());

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