Compare commits

..

1 Commits

Author SHA1 Message Date
Brian Clozel fe00848be2 Release v7.0.0 2025-11-13 11:06:28 +01:00
54 changed files with 544 additions and 1004 deletions
@@ -18,7 +18,6 @@ package org.springframework.build.multirelease;
import javax.inject.Inject;
import org.gradle.api.JavaVersion;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ConfigurationContainer;
@@ -65,12 +64,10 @@ public class MultiReleaseJarPlugin implements Plugin<Project> {
dependencies,
objects);
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_25)) {
TaskProvider<MultiReleaseJarValidateTask> validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
});
tasks.named("check", task -> task.dependsOn(validateJarTask));
}
TaskProvider<MultiReleaseJarValidateTask> validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
});
tasks.named("check", task -> task.dependsOn(validateJarTask));
}
}
@@ -29,8 +29,6 @@ import org.gradle.testkit.runner.GradleRunner;
import org.gradle.testkit.runner.UnexpectedBuildFailure;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.io.TempDir;
import static org.assertj.core.api.Assertions.assertThat;
@@ -45,13 +43,10 @@ public class MultiReleaseJarPluginTests {
private File buildFile;
private File propertiesFile;
@BeforeEach
void setup(@TempDir File projectDir) {
this.projectDir = projectDir;
this.buildFile = new File(this.projectDir, "build.gradle");
this.propertiesFile = new File(this.projectDir, "gradle.properties");
}
@Test
@@ -124,7 +119,6 @@ public class MultiReleaseJarPluginTests {
}
@Test
@DisabledForJreRange(max = JRE.JAVA_24, disabledReason = "'jar --validate' is available as of Java 25")
void validateJar() throws IOException {
writeBuildFile("""
plugins {
@@ -137,9 +131,6 @@ public class MultiReleaseJarPluginTests {
}
multiRelease { releaseVersions 17 }
""");
writeGradleProperties("""
org.gradle.jvmargs=-Duser.language=en
""");
writeClass("src/main/java17", "Main.java", """
public class Main {
@@ -161,12 +152,6 @@ public class MultiReleaseJarPluginTests {
}
}
private void writeGradleProperties(String properties) throws IOException {
try (PrintWriter out = new PrintWriter(new FileWriter(this.propertiesFile))) {
out.print(properties);
}
}
private void writeClass(String path, String fileName, String fileContent) throws IOException {
Path folder = this.projectDir.toPath().resolve(path);
Files.createDirectories(folder);
+1 -1
View File
@@ -36,4 +36,4 @@ runtime:
failure_level: warn
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.20/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.19/ui-bundle.zip
@@ -184,10 +184,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
standaloneSetup(SimpleController())
.alwaysExpect<StandaloneMockMvcBuilder>(status().isOk())
.alwaysExpect<StandaloneMockMvcBuilder>(content().contentType("application/json;charset=UTF-8"))
.build()
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
@@ -18,7 +18,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
mockMvc = standaloneSetup(PersonController()).addFilters<StandaloneMockMvcBuilder>(CharacterEncodingFilter()).build()
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
@@ -159,18 +159,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class MyWebTests {
lateinit var mockMvc: MockMvc
@BeforeEach
fun setup() {
mockMvc = standaloneSetup(AccountController())
.defaultRequest<StandaloneMockMvcBuilder>(get("/")
.contextPath("/app").servletPath("/main")
.accept(MediaType.APPLICATION_JSON)).build()
}
}
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
@@ -25,13 +25,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
// static import of MockMvcBuilders.standaloneSetup
val mockMvc = standaloneSetup(MusicController())
.defaultRequest<StandaloneMockMvcBuilder>(get("/").accept(MediaType.APPLICATION_JSON))
.alwaysExpect<StandaloneMockMvcBuilder>(status().isOk())
.alwaysExpect<StandaloneMockMvcBuilder>(content().contentType("application/json;charset=UTF-8"))
.build()
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
@@ -59,13 +53,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
// static import of SharedHttpSessionConfigurer.sharedHttpSession
val mockMvc = MockMvcBuilders.standaloneSetup(TestController())
.apply<StandaloneMockMvcBuilder>(sharedHttpSession())
.build()
// Use mockMvc to perform requests...
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
@@ -8,9 +8,9 @@ use https://www.gebish.org/[Geb] to make our tests even Groovy-er.
== Why Geb and MockMvc?
Geb is backed by WebDriver, so it offers many of the
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[same benefits]
that we get from WebDriver. However, Geb makes things even easier by taking care of some
of the boilerplate code for us.
xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-why[same benefits] that we get from
WebDriver. However, Geb makes things even easier by taking care of some of the
boilerplate code for us.
[[mockmvc-server-htmlunit-geb-setup]]
== MockMvc and Geb Setup
@@ -28,8 +28,7 @@ def setup() {
----
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
usage, see
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
This ensures that any URL referencing `localhost` as the server is directed to our
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
@@ -63,10 +62,10 @@ forwarded to the current page object. This removes a lot of the boilerplate code
needed when using WebDriver directly.
As with direct WebDriver usage, this improves on the design of our
xref:testing/mockmvc/htmlunit/mah.adoc#mockmvc-server-htmlunit-mah-usage[HtmlUnit test]
by using the Page Object Pattern. As mentioned previously, we can use the Page Object
Pattern with HtmlUnit and WebDriver, but it is even easier with Geb. Consider our new
Groovy-based `CreateMessagePage` implementation:
xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test] by using the Page Object
Pattern. As mentioned previously, we can use the Page Object Pattern with HtmlUnit and
WebDriver, but it is even easier with Geb. Consider our new Groovy-based
`CreateMessagePage` implementation:
[source,groovy]
----
@@ -7,7 +7,8 @@ to use the raw HtmlUnit libraries.
[[mockmvc-server-htmlunit-mah-setup]]
== MockMvc and HtmlUnit Setup
First, make sure that you have included a test dependency on `org.htmlunit:htmlunit`.
First, make sure that you have included a test dependency on
`org.htmlunit:htmlunit`.
We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by using the
`MockMvcWebClientBuilder`, as follows:
@@ -44,7 +45,7 @@ Kotlin::
======
NOTE: This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage,
see <<mockmvc-server-htmlunit-mah-advanced-builder>>.
see xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
This ensures that any URL that references `localhost` as the server is directed to our
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
@@ -76,7 +77,7 @@ Kotlin::
======
NOTE: The default context path is `""`. Alternatively, we can specify the context path,
as described in <<mockmvc-server-htmlunit-mah-advanced-builder>>.
as described in xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
Once we have a reference to the `HtmlPage`, we can then fill out the form and submit it
to create a message, as the following example shows:
@@ -143,10 +144,10 @@ Kotlin::
======
The preceding code improves on our
xref:testing/mockmvc/htmlunit/why.adoc#mockmvc-server-htmlunit-why[MockMvc test] in a
number of ways. First, we no longer have to explicitly verify our form and then create a
request that looks like the form. Instead, we request the form, fill it out, and submit
it, thereby significantly reducing the overhead.
xref:testing/mockmvc/htmlunit/why.adoc#spring-mvc-test-server-htmlunit-mock-mvc-test[MockMvc test] in a number of ways.
First, we no longer have to explicitly verify our form and then create a request that
looks like the form. Instead, we request the form, fill it out, and submit it, thereby
significantly reducing the overhead.
Another important factor is that https://htmlunit.sourceforge.io/javascript.html[HtmlUnit
uses the Mozilla Rhino engine] to evaluate JavaScript. This means that we can also test
@@ -266,19 +267,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply<DefaultMockMvcBuilder>(springSecurity())
.build()
webClient = MockMvcWebClientBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com", "example.org")
.build()
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
@@ -203,7 +203,7 @@ Kotlin::
======
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
usage, see <<mockmvc-server-htmlunit-webdriver-advanced-builder>>.
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
The preceding example ensures that any URL that references `localhost` as the server is
directed to our `MockMvc` instance without the need for a real HTTP connection. Any other
@@ -259,11 +259,10 @@ Kotlin::
======
--
This improves on the design of our
xref:testing/mockmvc/htmlunit/mah.adoc#mockmvc-server-htmlunit-mah-usage[HtmlUnit test]
This improves on the design of our xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test]
by leveraging the Page Object Pattern. As we mentioned in
<<mockmvc-server-htmlunit-webdriver-why>>, we can use the Page Object Pattern with
HtmlUnit, but it is much easier with WebDriver. Consider the following
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[Why WebDriver and MockMvc?], we can use the Page Object Pattern
with HtmlUnit, but it is much easier with WebDriver. Consider the following
`CreateMessagePage` implementation:
--
@@ -563,19 +562,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val mockMvc: MockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply<DefaultMockMvcBuilder>(springSecurity())
.build()
driver = MockMvcHtmlUnitDriverBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com", "example.org")
.build()
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
@@ -60,7 +60,7 @@ assume our form looks like the following snippet:
</form>
----
How do we ensure that our form produces the correct request to create a new message? A
How do we ensure that our form produce the correct request to create a new message? A
naive attempt might resemble the following:
[tabs]
@@ -154,7 +154,7 @@ validation.
[[mockmvc-server-htmlunit-why-integration]]
== Integration Testing to the Rescue?
To resolve the issues mentioned above, we could perform end-to-end integration testing,
To resolve the issues mentioned earlier, we could perform end-to-end integration testing,
but this has some drawbacks. Consider testing the view that lets us page through the
messages. We might need the following tests:
@@ -171,7 +171,7 @@ leads to a number of additional challenges:
* Testing can become slow, since each test would need to ensure that the database is in
the correct state.
* Since our database needs to be in a specific state, we cannot run tests in parallel.
* Performing assertions on items such as auto-generated IDs, timestamps, and others can
* Performing assertions on such items as auto-generated IDs, timestamps, and others can
be difficult.
These challenges do not mean that we should abandon end-to-end integration testing
+1 -1
View File
@@ -129,7 +129,7 @@ dependencies {
api("org.jboss.logging:jboss-logging:3.6.1.Final")
api("org.jruby:jruby:10.0.2.0")
api("org.jspecify:jspecify:1.0.0")
api("org.junit.support:testng-engine:1.1.0")
api("org.junit.support:testng-engine:1.0.5")
api("org.mozilla:rhino:1.7.15")
api("org.ogce:xpp3:1.1.6")
api("org.python:jython-standalone:2.7.4")
+1 -1
View File
@@ -1,4 +1,4 @@
version=7.0.1
version=7.0.0
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
@@ -33,6 +33,7 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller
* @author Rob Harrop
* @since 1.1
* @see PropertiesBeanDefinitionReader
* @see org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
*/
public abstract class BeanDefinitionReaderUtils {
@@ -43,6 +43,7 @@ import org.springframework.core.AliasRegistry;
* @see DefaultListableBeanFactory
* @see org.springframework.context.support.GenericApplicationContext
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see PropertiesBeanDefinitionReader
*/
public interface BeanDefinitionRegistry extends AliasRegistry {
@@ -76,7 +76,7 @@ import org.springframework.util.StringUtils;
* @since 26.11.2003
* @see DefaultListableBeanFactory
* @deprecated in favor of Spring's common bean definition formats and/or
* custom BeanDefinitionReader implementations
* custom reader implementations
*/
@Deprecated(since = "5.3")
public class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader {
@@ -46,14 +46,14 @@ class YamlMapFactoryBeanTests {
@Test
void testSetIgnoreResourceNotFound() {
this.factory.setResolutionMethod(YamlMapFactoryBean.ResolutionMethod.OVERRIDE_AND_IGNORE);
this.factory.setResources(new FileSystemResource("non-existent-file.yml"));
this.factory.setResources(new FileSystemResource("non-exsitent-file.yml"));
assertThat(this.factory.getObject()).isEmpty();
}
@Test
void testSetBarfOnResourceNotFound() {
assertThatIllegalStateException().isThrownBy(() -> {
this.factory.setResources(new FileSystemResource("non-existent-file.yml"));
this.factory.setResources(new FileSystemResource("non-exsitent-file.yml"));
this.factory.getObject().size();
});
}
@@ -51,7 +51,7 @@ class MetadataAttachmentTests {
void metadataIsInherited() {
BeanDefinition beanDefinition = this.beanFactory.getMergedBeanDefinition("testBean2");
assertThat(beanDefinition.getAttribute("foo")).as("Metadata not inherited").isEqualTo("bar");
assertThat(beanDefinition.getAttribute("abc")).as("Child metadata not attached").isEqualTo("123");
assertThat(beanDefinition.getAttribute("abc")).as("Child metdata not attached").isEqualTo("123");
}
@Test
@@ -76,7 +76,7 @@ public class CaffeineCacheManager implements CacheManager {
private boolean allowNullValues = true;
private volatile boolean dynamic = true;
private boolean dynamic = true;
private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
@@ -101,15 +101,10 @@ public class CaffeineCacheManager implements CacheManager {
/**
* Specify the set of cache names for this CacheManager's 'static' mode.
* <p>The number of caches and their names will be fixed after a call
* to this method, with no creation of further cache regions at runtime.
* <p>Note that this method replaces existing caches of the given names
* and prevents the creation of further cache regions from here on - but
* does <i>not</i> remove unrelated existing caches. For a full reset,
* consider calling {@link #resetCaches()} before calling this method.
* <p>Calling this method with a {@code null} collection argument resets
* the mode to 'dynamic', allowing for further creation of caches again.
* @see #resetCaches()
* <p>The number of caches and their names will be fixed after a call to this method,
* with no creation of further cache regions at runtime.
* <p>Calling this with a {@code null} collection argument resets the
* mode to 'dynamic', allowing for further creation of caches again.
*/
public void setCacheNames(@Nullable Collection<String> cacheNames) {
if (cacheNames != null) {
@@ -249,6 +244,11 @@ public class CaffeineCacheManager implements CacheManager {
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
@Override
public @Nullable Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
@@ -258,33 +258,6 @@ public class CaffeineCacheManager implements CacheManager {
return cache;
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
/**
* Reset this cache manager's caches, removing them completely for on-demand
* re-creation in 'dynamic' mode, or simply clearing their entries otherwise.
* @since 6.2.14
*/
public void resetCaches() {
this.cacheMap.values().forEach(Cache::clear);
if (this.dynamic) {
this.cacheMap.keySet().retainAll(this.customCacheNames);
}
}
/**
* Remove the specified cache from this cache manager, applying to
* custom caches as well as dynamically registered caches at runtime.
* @param name the name of the cache
* @since 6.1.15
*/
public void removeCache(String name) {
this.customCacheNames.remove(name);
this.cacheMap.remove(name);
}
/**
* Register the given native Caffeine Cache instance with this cache manager,
@@ -328,6 +301,16 @@ public class CaffeineCacheManager implements CacheManager {
this.cacheMap.put(name, adaptCaffeineCache(name, cache));
}
/**
* Remove the specified cache from this cache manager, applying to
* custom caches as well as dynamically registered caches at runtime.
* @param name the name of the cache
* @since 6.1.15
*/
public void removeCache(String name) {
this.customCacheNames.remove(name);
this.cacheMap.remove(name);
}
/**
* Adapt the given new native Caffeine Cache instance to Spring's {@link Cache}
@@ -26,7 +26,7 @@ import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.Aware;
import org.springframework.core.MethodClassKey;
import org.springframework.util.ReflectionUtils;
@@ -95,8 +95,8 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Skip setBeanFactory method on BeanFactoryAware.
if (method.getDeclaringClass() == BeanFactoryAware.class) {
// Skip methods declared on BeanFactoryAware and co.
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
return null;
}
@@ -24,6 +24,7 @@ import com.github.benmanes.caffeine.cache.CaffeineSpec;
import org.junit.jupiter.api.Test;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.SimpleValueWrapper;
import static org.assertj.core.api.Assertions.assertThat;
@@ -41,7 +42,7 @@ class CaffeineCacheManagerTests {
@Test
@SuppressWarnings("cast")
void dynamicMode() {
CaffeineCacheManager cm = new CaffeineCacheManager();
CacheManager cm = new CaffeineCacheManager();
Cache cache1 = cm.getCache("c1");
assertThat(cache1).isInstanceOf(CaffeineCache.class);
@@ -75,14 +76,6 @@ class CaffeineCacheManagerTests {
cache1.evict("key3");
assertThat(cache1.get("key3", () -> (String) null)).isNull();
assertThat(cache1.get("key3", () -> (String) null)).isNull();
cm.removeCache("c1");
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
assertThat(cm.getCache("c2")).isSameAs(cache2);
cm.resetCaches();
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
assertThat(cm.getCache("c2")).isNotSameAs(cache2);
}
@Test
@@ -138,24 +131,11 @@ class CaffeineCacheManagerTests {
cm.setAllowNullValues(true);
Cache cache1y = cm.getCache("c1");
Cache cache2y = cm.getCache("c2");
cache1y.put("key3", null);
assertThat(cache1y.get("key3").get()).isNull();
cache1y.evict("key3");
assertThat(cache1y.get("key3")).isNull();
cache2y.put("key4", "value4");
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
cm.removeCache("c1");
assertThat(cm.getCache("c1")).isNull();
assertThat(cm.getCache("c2")).isSameAs(cache2y);
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
cm.resetCaches();
assertThat(cm.getCache("c1")).isNull();
assertThat(cm.getCache("c2")).isSameAs(cache2y);
assertThat(cache2y.get("key4")).isNull();
}
@Test
@@ -55,7 +55,7 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
private volatile boolean dynamic = true;
private boolean dynamic = true;
private boolean allowNullValues = true;
@@ -82,15 +82,10 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
/**
* Specify the set of cache names for this CacheManager's 'static' mode.
* <p>The number of caches and their names will be fixed after a call
* to this method, with no creation of further cache regions at runtime.
* <p>Note that this method replaces existing caches of the given names
* and prevents the creation of further cache regions from here on - but
* does <i>not</i> remove unrelated existing caches. For a full reset,
* consider calling {@link #resetCaches()} before calling this method.
* <p>Calling this method with a {@code null} collection argument resets
* the mode to 'dynamic', allowing for further creation of caches again.
* @see #resetCaches()
* <p>The number of caches and their names will be fixed after a call to this method,
* with no creation of further cache regions at runtime.
* <p>Calling this with a {@code null} collection argument resets the
* mode to 'dynamic', allowing for further creation of caches again.
*/
public void setCacheNames(@Nullable Collection<String> cacheNames) {
if (cacheNames != null) {
@@ -165,6 +160,11 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
@Override
public @Nullable Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
@@ -174,23 +174,6 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
return cache;
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
/**
* Reset this cache manager's caches, removing them completely for on-demand
* re-creation in 'dynamic' mode, or simply clearing their entries otherwise.
* @since 6.2.14
*/
public void resetCaches() {
this.cacheMap.values().forEach(Cache::clear);
if (this.dynamic) {
this.cacheMap.clear();
}
}
/**
* Remove the specified cache from this cache manager.
* @param name the name of the cache
@@ -28,7 +28,7 @@ import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.Aware;
import org.springframework.core.MethodClassKey;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
@@ -137,8 +137,8 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Skip setBeanFactory method on BeanFactoryAware.
if (method.getDeclaringClass() == BeanFactoryAware.class) {
// Skip methods declared on BeanFactoryAware and co.
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
return null;
}
@@ -18,6 +18,8 @@ package org.springframework.cache.support;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -31,28 +33,44 @@ import org.springframework.cache.CacheManager;
* for disabling caching, typically used for backing cache declarations
* without an actual backing store.
*
* <p>This implementation will simply accept any items into the cache,
* not actually storing them.
* <p>Will simply accept any items into the cache not actually storing them.
*
* @author Costin Leau
* @author Stephane Nicoll
* @author Juergen Hoeller
* @since 3.1
* @see NoOpCache
*/
public class NoOpCacheManager implements CacheManager {
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<>(16);
private final Set<String> cacheNames = new LinkedHashSet<>(16);
/**
* This implementation always returns a {@link Cache} implementation that will not store items.
* Additionally, the request cache will be remembered by the manager for consistency.
*/
@Override
public @Nullable Cache getCache(String name) {
return this.cacheMap.computeIfAbsent(name, NoOpCache::new);
Cache cache = this.caches.get(name);
if (cache == null) {
this.caches.computeIfAbsent(name, NoOpCache::new);
synchronized (this.cacheNames) {
this.cacheNames.add(name);
}
}
return this.caches.get(name);
}
/**
* This implementation returns the name of the caches previously requested.
*/
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
synchronized (this.cacheNames) {
return Collections.unmodifiableSet(this.cacheNames);
}
}
}
@@ -225,6 +225,7 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl
* @param beanFactory the bean factory to load bean definitions into
* @throws BeansException if parsing of the bean definitions failed
* @throws IOException if loading of bean definition files failed
* @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
*/
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
@@ -81,6 +81,8 @@ import org.springframework.util.Assert;
* GenericApplicationContext ctx = new GenericApplicationContext();
* XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
* xmlReader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
* PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(ctx);
* propReader.loadBeanDefinitions(new ClassPathResource("otherBeans.properties"));
* ctx.refresh();
*
* MyBean myBean = (MyBean) ctx.getBean("myBean");
@@ -102,6 +104,7 @@ import org.springframework.util.Assert;
* @see #registerBeanDefinition
* @see #refresh()
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
*/
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
@@ -19,6 +19,7 @@ package org.springframework.cache.concurrent;
import org.junit.jupiter.api.Test;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import static org.assertj.core.api.Assertions.assertThat;
@@ -30,7 +31,7 @@ class ConcurrentMapCacheManagerTests {
@Test
void testDynamicMode() {
ConcurrentMapCacheManager cm = new ConcurrentMapCacheManager();
CacheManager cm = new ConcurrentMapCacheManager();
Cache cache1 = cm.getCache("c1");
assertThat(cache1).isInstanceOf(ConcurrentMapCache.class);
Cache cache1again = cm.getCache("c1");
@@ -64,14 +65,6 @@ class ConcurrentMapCacheManagerTests {
assertThat(cache1.get("key3").get()).isNull();
cache1.evict("key3");
assertThat(cache1.get("key3")).isNull();
cm.removeCache("c1");
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
assertThat(cm.getCache("c2")).isSameAs(cache2);
cm.resetCaches();
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
assertThat(cm.getCache("c2")).isNotSameAs(cache2);
}
@Test
@@ -114,24 +107,11 @@ class ConcurrentMapCacheManagerTests {
cm.setAllowNullValues(true);
Cache cache1y = cm.getCache("c1");
Cache cache2y = cm.getCache("c2");
cache1y.put("key3", null);
assertThat(cache1y.get("key3").get()).isNull();
cache1y.evict("key3");
assertThat(cache1y.get("key3")).isNull();
cache2y.put("key4", "value4");
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
cm.removeCache("c1");
assertThat(cm.getCache("c1")).isNull();
assertThat(cm.getCache("c2")).isSameAs(cache2y);
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
cm.resetCaches();
assertThat(cm.getCache("c1")).isNull();
assertThat(cm.getCache("c2")).isSameAs(cache2y);
assertThat(cache2y.get("key4")).isNull();
}
@Test
@@ -92,7 +92,7 @@ class CommonAnnotationBeanRegistrationAotContributionTests {
@Test
@CompileWithForkedClassLoader
void contributeWhenPackagePrivateFieldInjectionInjectsUsingFieldAssignment() {
void contributeWhenPackagePrivateFieldInjectionInjectsUsingFieldAssignement() {
this.beanFactory.registerSingleton("one", "1");
this.beanFactory.registerSingleton("two", "2");
RegisteredBean registeredBean = getAndApplyContribution(
@@ -16,16 +16,19 @@
package org.springframework.scheduling.support;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedClass;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.FieldSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.scheduling.TriggerContext;
@@ -41,55 +44,51 @@ import static org.junit.jupiter.params.provider.Arguments.arguments;
* @author Juergen Hoeller
* @author Sam Brannen
*/
@ParameterizedClass
@FieldSource("parameters")
@SuppressWarnings("deprecation")
class CronTriggerTests {
static List<Arguments> parameters = List.of(
arguments(new Date(), TimeZone.getTimeZone("PST")),
arguments(new Date(), TimeZone.getTimeZone("CET")));
private final Calendar calendar = new GregorianCalendar();
private final Date localDateTime;
private final TimeZone timeZone;
CronTriggerTests(Date localDateTime, TimeZone timeZone) {
private void setup(Date localDateTime, TimeZone timeZone) {
this.calendar.setTime(localDateTime);
this.calendar.setTimeZone(timeZone);
roundup(this.calendar);
this.localDateTime = localDateTime;
this.timeZone = timeZone;
}
@Test
void matchAll() {
@ParameterizedCronTriggerTest
void matchAll(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("* * * * * *", timeZone);
TriggerContext context = getTriggerContext(localDateTime);
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
}
@Test
void matchLastSecond() {
@ParameterizedCronTriggerTest
void matchLastSecond(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("* * * * * *", timeZone);
GregorianCalendar calendar = new GregorianCalendar();
calendar.set(Calendar.SECOND, 58);
assertMatchesNextSecond(trigger, calendar);
}
@Test
void matchSpecificSecond() {
@ParameterizedCronTriggerTest
void matchSpecificSecond(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("10 * * * * *", timeZone);
GregorianCalendar calendar = new GregorianCalendar();
calendar.set(Calendar.SECOND, 9);
assertMatchesNextSecond(trigger, calendar);
}
@Test
void incrementSecondByOne() {
@ParameterizedCronTriggerTest
void incrementSecondByOne(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("11 * * * * *", timeZone);
this.calendar.set(Calendar.SECOND, 10);
Date localDate = this.calendar.getTime();
@@ -98,8 +97,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
}
@Test
void incrementSecondWithPreviousExecutionTooEarly() {
@ParameterizedCronTriggerTest
void incrementSecondWithPreviousExecutionTooEarly(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("11 * * * * *", timeZone);
this.calendar.set(Calendar.SECOND, 11);
SimpleTriggerContext context = new SimpleTriggerContext();
@@ -109,8 +110,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
}
@Test
void incrementSecondAndRollover() {
@ParameterizedCronTriggerTest
void incrementSecondAndRollover(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("10 * * * * *", timeZone);
this.calendar.set(Calendar.SECOND, 11);
Date localDate = this.calendar.getTime();
@@ -119,8 +122,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
}
@Test
void secondRange() {
@ParameterizedCronTriggerTest
void secondRange(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("10-15 * * * * *", timeZone);
this.calendar.set(Calendar.SECOND, 9);
assertMatchesNextSecond(trigger, this.calendar);
@@ -128,8 +133,10 @@ class CronTriggerTests {
assertMatchesNextSecond(trigger, this.calendar);
}
@Test
void incrementMinute() {
@ParameterizedCronTriggerTest
void incrementMinute(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 * * * * *", timeZone);
this.calendar.set(Calendar.MINUTE, 10);
Date localDate = this.calendar.getTime();
@@ -144,8 +151,10 @@ class CronTriggerTests {
assertThat(localDate).isEqualTo(this.calendar.getTime());
}
@Test
void incrementMinuteByOne() {
@ParameterizedCronTriggerTest
void incrementMinuteByOne(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 11 * * * *", timeZone);
this.calendar.set(Calendar.MINUTE, 10);
TriggerContext context = getTriggerContext(this.calendar.getTime());
@@ -154,8 +163,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
}
@Test
void incrementMinuteAndRollover() {
@ParameterizedCronTriggerTest
void incrementMinuteAndRollover(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 10 * * * *", timeZone);
this.calendar.set(Calendar.MINUTE, 11);
this.calendar.set(Calendar.SECOND, 0);
@@ -165,8 +176,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
}
@Test
void incrementHour() {
@ParameterizedCronTriggerTest
void incrementHour(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 0 * * * *", timeZone);
this.calendar.set(Calendar.MONTH, 9);
this.calendar.set(Calendar.DAY_OF_MONTH, 30);
@@ -184,8 +197,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context2)).isEqualTo(this.calendar.getTime());
}
@Test
void incrementHourAndRollover() {
@ParameterizedCronTriggerTest
void incrementHourAndRollover(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 0 * * * *", timeZone);
this.calendar.set(Calendar.MONTH, 9);
this.calendar.set(Calendar.DAY_OF_MONTH, 10);
@@ -204,8 +219,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context2)).isEqualTo(this.calendar.getTime());
}
@Test
void incrementDayOfMonth() {
@ParameterizedCronTriggerTest
void incrementDayOfMonth(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 0 0 * * *", timeZone);
this.calendar.set(Calendar.DAY_OF_MONTH, 1);
Date localDate = this.calendar.getTime();
@@ -224,8 +241,10 @@ class CronTriggerTests {
assertThat(this.calendar.get(Calendar.DAY_OF_MONTH)).isEqualTo(3);
}
@Test
void incrementDayOfMonthByOne() {
@ParameterizedCronTriggerTest
void incrementDayOfMonthByOne(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("* * * 10 * *", timeZone);
this.calendar.set(Calendar.DAY_OF_MONTH, 9);
Date localDate = this.calendar.getTime();
@@ -237,8 +256,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
}
@Test
void incrementDayOfMonthAndRollover() {
@ParameterizedCronTriggerTest
void incrementDayOfMonthAndRollover(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("* * * 10 * *", timeZone);
this.calendar.set(Calendar.DAY_OF_MONTH, 11);
Date localDate = this.calendar.getTime();
@@ -251,8 +272,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
}
@Test
void dailyTriggerInShortMonth() {
@ParameterizedCronTriggerTest
void dailyTriggerInShortMonth(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 0 0 * * *", timeZone);
this.calendar.set(Calendar.MONTH, 8); // September: 30 days
this.calendar.set(Calendar.DAY_OF_MONTH, 30);
@@ -270,8 +293,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context2)).isEqualTo(this.calendar.getTime());
}
@Test
void dailyTriggerInLongMonth() {
@ParameterizedCronTriggerTest
void dailyTriggerInLongMonth(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 0 0 * * *", timeZone);
this.calendar.set(Calendar.MONTH, 7); // August: 31 days and not a daylight saving boundary
this.calendar.set(Calendar.DAY_OF_MONTH, 30);
@@ -289,8 +314,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context2)).isEqualTo(this.calendar.getTime());
}
@Test
void dailyTriggerOnDaylightSavingBoundary() {
@ParameterizedCronTriggerTest
void dailyTriggerOnDaylightSavingBoundary(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 0 0 * * *", timeZone);
this.calendar.set(Calendar.MONTH, 9); // October: 31 days and a daylight saving boundary in CET
this.calendar.set(Calendar.DAY_OF_MONTH, 30);
@@ -308,8 +335,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context2)).isEqualTo(this.calendar.getTime());
}
@Test
void incrementMonth() {
@ParameterizedCronTriggerTest
void incrementMonth(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 0 0 1 * *", timeZone);
this.calendar.set(Calendar.MONTH, 9);
this.calendar.set(Calendar.DAY_OF_MONTH, 30);
@@ -327,8 +356,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context2)).isEqualTo(this.calendar.getTime());
}
@Test
void incrementMonthAndRollover() {
@ParameterizedCronTriggerTest
void incrementMonthAndRollover(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 0 0 1 * *", timeZone);
this.calendar.set(Calendar.MONTH, 11);
this.calendar.set(Calendar.DAY_OF_MONTH, 31);
@@ -348,8 +379,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context2)).isEqualTo(this.calendar.getTime());
}
@Test
void monthlyTriggerInLongMonth() {
@ParameterizedCronTriggerTest
void monthlyTriggerInLongMonth(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 0 0 31 * *", timeZone);
this.calendar.set(Calendar.MONTH, 9);
this.calendar.set(Calendar.DAY_OF_MONTH, 30);
@@ -362,8 +395,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
}
@Test
void monthlyTriggerInShortMonth() {
@ParameterizedCronTriggerTest
void monthlyTriggerInShortMonth(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 0 0 1 * *", timeZone);
this.calendar.set(Calendar.MONTH, 9);
this.calendar.set(Calendar.DAY_OF_MONTH, 30);
@@ -377,8 +412,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
}
@Test
void incrementDayOfWeekByOne() {
@ParameterizedCronTriggerTest
void incrementDayOfWeekByOne(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("* * * * * 2", timeZone);
this.calendar.set(Calendar.DAY_OF_WEEK, 2);
Date localDate = this.calendar.getTime();
@@ -391,8 +428,10 @@ class CronTriggerTests {
assertThat(this.calendar.get(Calendar.DAY_OF_WEEK)).isEqualTo(Calendar.TUESDAY);
}
@Test
void incrementDayOfWeekAndRollover() {
@ParameterizedCronTriggerTest
void incrementDayOfWeekAndRollover(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("* * * * * 2", timeZone);
this.calendar.set(Calendar.DAY_OF_WEEK, 4);
Date localDate = this.calendar.getTime();
@@ -405,8 +444,10 @@ class CronTriggerTests {
assertThat(this.calendar.get(Calendar.DAY_OF_WEEK)).isEqualTo(Calendar.TUESDAY);
}
@Test
void specificMinuteSecond() {
@ParameterizedCronTriggerTest
void specificMinuteSecond(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("55 5 * * * *", timeZone);
this.calendar.set(Calendar.MINUTE, 4);
this.calendar.set(Calendar.SECOND, 54);
@@ -422,8 +463,10 @@ class CronTriggerTests {
assertThat(actual).isEqualTo(this.calendar.getTime());
}
@Test
void specificHourSecond() {
@ParameterizedCronTriggerTest
void specificHourSecond(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("55 * 10 * * *", timeZone);
this.calendar.set(Calendar.HOUR_OF_DAY, 9);
this.calendar.set(Calendar.SECOND, 54);
@@ -440,8 +483,10 @@ class CronTriggerTests {
assertThat(actual).isEqualTo(this.calendar.getTime());
}
@Test
void specificMinuteHour() {
@ParameterizedCronTriggerTest
void specificMinuteHour(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("* 5 10 * * *", timeZone);
this.calendar.set(Calendar.MINUTE, 4);
this.calendar.set(Calendar.HOUR_OF_DAY, 9);
@@ -459,8 +504,10 @@ class CronTriggerTests {
assertThat(actual).isEqualTo(this.calendar.getTime());
}
@Test
void specificDayOfMonthSecond() {
@ParameterizedCronTriggerTest
void specificDayOfMonthSecond(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("55 * * 3 * *", timeZone);
this.calendar.set(Calendar.DAY_OF_MONTH, 2);
this.calendar.set(Calendar.SECOND, 54);
@@ -478,8 +525,10 @@ class CronTriggerTests {
assertThat(actual).isEqualTo(this.calendar.getTime());
}
@Test
void specificDate() {
@ParameterizedCronTriggerTest
void specificDate(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("* * * 3 11 *", timeZone);
this.calendar.set(Calendar.DAY_OF_MONTH, 2);
this.calendar.set(Calendar.MONTH, 9);
@@ -498,8 +547,10 @@ class CronTriggerTests {
assertThat(actual).isEqualTo(this.calendar.getTime());
}
@Test
void nonExistentSpecificDate() {
@ParameterizedCronTriggerTest
void nonExistentSpecificDate(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
// TODO: maybe try and detect this as a special case in parser?
CronTrigger trigger = new CronTrigger("0 0 0 31 6 *", timeZone);
this.calendar.set(Calendar.DAY_OF_MONTH, 10);
@@ -509,8 +560,10 @@ class CronTriggerTests {
assertThat(trigger.nextExecutionTime(context1)).isNull();
}
@Test
void leapYearSpecificDate() {
@ParameterizedCronTriggerTest
void leapYearSpecificDate(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 0 0 29 2 *", timeZone);
this.calendar.set(Calendar.YEAR, 2007);
this.calendar.set(Calendar.DAY_OF_MONTH, 10);
@@ -530,8 +583,10 @@ class CronTriggerTests {
assertThat(actual).isEqualTo(this.calendar.getTime());
}
@Test
void weekDaySequence() {
@ParameterizedCronTriggerTest
void weekDaySequence(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 0 7 ? * MON-FRI", timeZone);
// This is a Saturday
this.calendar.set(2009, 8, 26);
@@ -556,138 +611,184 @@ class CronTriggerTests {
assertThat(actual).isEqualTo(this.calendar.getTime());
}
@Test
void dayOfWeekIndifferent() {
@ParameterizedCronTriggerTest
void dayOfWeekIndifferent(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger1 = new CronTrigger("* * * 2 * *", timeZone);
CronTrigger trigger2 = new CronTrigger("* * * 2 * ?", timeZone);
assertThat(trigger2).isEqualTo(trigger1);
}
@Test
void secondIncrementer() {
@ParameterizedCronTriggerTest
void secondIncrementer(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger1 = new CronTrigger("57,59 * * * * *", timeZone);
CronTrigger trigger2 = new CronTrigger("57/2 * * * * *", timeZone);
assertThat(trigger2).isEqualTo(trigger1);
}
@Test
void secondIncrementerWithRange() {
@ParameterizedCronTriggerTest
void secondIncrementerWithRange(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger1 = new CronTrigger("1,3,5 * * * * *", timeZone);
CronTrigger trigger2 = new CronTrigger("1-6/2 * * * * *", timeZone);
assertThat(trigger2).isEqualTo(trigger1);
}
@Test
void hourIncrementer() {
@ParameterizedCronTriggerTest
void hourIncrementer(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger1 = new CronTrigger("* * 4,8,12,16,20 * * *", timeZone);
CronTrigger trigger2 = new CronTrigger("* * 4/4 * * *", timeZone);
assertThat(trigger2).isEqualTo(trigger1);
}
@Test
void dayNames() {
@ParameterizedCronTriggerTest
void dayNames(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger1 = new CronTrigger("* * * * * 0-6", timeZone);
CronTrigger trigger2 = new CronTrigger("* * * * * TUE,WED,THU,FRI,SAT,SUN,MON", timeZone);
assertThat(trigger2).isEqualTo(trigger1);
}
@Test
void sundayIsZero() {
@ParameterizedCronTriggerTest
void sundayIsZero(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger1 = new CronTrigger("* * * * * 0", timeZone);
CronTrigger trigger2 = new CronTrigger("* * * * * SUN", timeZone);
assertThat(trigger2).isEqualTo(trigger1);
}
@Test
void sundaySynonym() {
@ParameterizedCronTriggerTest
void sundaySynonym(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger1 = new CronTrigger("* * * * * 0", timeZone);
CronTrigger trigger2 = new CronTrigger("* * * * * 7", timeZone);
assertThat(trigger2).isEqualTo(trigger1);
}
@Test
void monthNames() {
@ParameterizedCronTriggerTest
void monthNames(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger1 = new CronTrigger("* * * * 1-12 *", timeZone);
CronTrigger trigger2 = new CronTrigger("* * * * FEB,JAN,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC *", timeZone);
assertThat(trigger2).isEqualTo(trigger1);
}
@Test
void monthNamesMixedCase() {
@ParameterizedCronTriggerTest
void monthNamesMixedCase(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger1 = new CronTrigger("* * * * 2 *", timeZone);
CronTrigger trigger2 = new CronTrigger("* * * * Feb *", timeZone);
assertThat(trigger2).isEqualTo(trigger1);
}
@Test
void secondInvalid() {
@ParameterizedCronTriggerTest
void secondInvalid(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("77 * * * * *", timeZone));
}
@Test
void secondRangeInvalid() {
@ParameterizedCronTriggerTest
void secondRangeInvalid(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("44-77 * * * * *", timeZone));
}
@Test
void minuteInvalid() {
@ParameterizedCronTriggerTest
void minuteInvalid(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("* 77 * * * *", timeZone));
}
@Test
void minuteRangeInvalid() {
@ParameterizedCronTriggerTest
void minuteRangeInvalid(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("* 44-77 * * * *", timeZone));
}
@Test
void hourInvalid() {
@ParameterizedCronTriggerTest
void hourInvalid(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("* * 27 * * *", timeZone));
}
@Test
void hourRangeInvalid() {
@ParameterizedCronTriggerTest
void hourRangeInvalid(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("* * 23-28 * * *", timeZone));
}
@Test
void dayInvalid() {
@ParameterizedCronTriggerTest
void dayInvalid(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("* * * 45 * *", timeZone));
}
@Test
void dayRangeInvalid() {
@ParameterizedCronTriggerTest
void dayRangeInvalid(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("* * * 28-45 * *", timeZone));
}
@Test
void monthInvalid() {
@ParameterizedCronTriggerTest
void monthInvalid(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("0 0 0 25 13 ?", timeZone));
}
@Test
void monthInvalidTooSmall() {
@ParameterizedCronTriggerTest
void monthInvalidTooSmall(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("0 0 0 25 0 ?", timeZone));
}
@Test
void dayOfMonthInvalid() {
@ParameterizedCronTriggerTest
void dayOfMonthInvalid(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("0 0 0 32 12 ?", timeZone));
}
@Test
void monthRangeInvalid() {
@ParameterizedCronTriggerTest
void monthRangeInvalid(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("* * * * 11-13 *", timeZone));
}
@Test
void whitespace() {
@ParameterizedCronTriggerTest
void whitespace(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger1 = new CronTrigger("* * * * 1 *", timeZone);
CronTrigger trigger2 = new CronTrigger("* * * * 1 *", timeZone);
assertThat(trigger2).isEqualTo(trigger1);
}
@Test
void monthSequence() {
@ParameterizedCronTriggerTest
void monthSequence(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
CronTrigger trigger = new CronTrigger("0 30 23 30 1/3 ?", timeZone);
this.calendar.set(2010, 11, 30);
Date localDate = this.calendar.getTime();
@@ -711,8 +812,10 @@ class CronTriggerTests {
assertThat(actual).isEqualTo(this.calendar.getTime());
}
@Test
void daylightSavingMissingHour() {
@ParameterizedCronTriggerTest
void daylightSavingMissingHour(Date localDateTime, TimeZone timeZone) {
setup(localDateTime, timeZone);
// This trigger has to be somewhere between 2:00 AM and 3:00 AM, so we
// use a cron expression for 2:10 AM every day.
CronTrigger trigger = new CronTrigger("0 10 2 * * *", timeZone);
@@ -762,4 +865,19 @@ class CronTriggerTests {
return new SimpleTriggerContext(null, null, lastCompletionTime);
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ParameterizedTest(name = "[{index}] localDateTime[{0}], time zone[{1}]")
@MethodSource("parameters")
@interface ParameterizedCronTriggerTest {
}
static Stream<Arguments> parameters() {
return Stream.of(
arguments(new Date(), TimeZone.getTimeZone("PST")),
arguments(new Date(), TimeZone.getTimeZone("CET"))
);
}
}
@@ -154,7 +154,7 @@ class InstrumentedMethodTests {
}
@Test
void classGetConstructorsShouldNotMatchWhenMissingTypeHint() {
void classGetConstructorsShouldNotMatchWhemMissingTypeHint() {
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors);
}
@@ -20,10 +20,8 @@ import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
@@ -31,8 +29,6 @@ import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -105,17 +101,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
/**
* Late binding entry set.
*/
private @Nullable Set<Map.Entry<K, V>> entrySet;
/**
* Late binding key set.
*/
private @Nullable Set<K> keySet;
/**
* Late binding values collection.
*/
private @Nullable Collection<V> values;
private volatile @Nullable Set<Map.Entry<K, V>> entrySet;
/**
@@ -526,26 +512,6 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
return entrySet;
}
@Override
public Set<K> keySet() {
Set<K> keySet = this.keySet;
if (keySet == null) {
keySet = new KeySet();
this.keySet = keySet;
}
return keySet;
}
@Override
public Collection<V> values() {
Collection<V> values = this.values;
if (values == null) {
values = new Values();
this.values = values;
}
return values;
}
private <T> @Nullable T doTask(@Nullable Object key, Task<T> task) {
int hash = getHash(key);
return getSegmentForHash(hash).doTask(hash, key, task);
@@ -974,7 +940,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
/**
* Internal entry-set implementation.
*/
private final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
@Override
public Iterator<Map.Entry<K, V>> iterator() {
@@ -1010,140 +976,13 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
public void clear() {
ConcurrentReferenceHashMap.this.clear();
}
@Override
public Spliterator<Map.Entry<K, V>> spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.CONCURRENT);
}
}
/**
* Internal key-set implementation.
*/
private final class KeySet extends AbstractSet<K> {
@Override
public Iterator<K> iterator() {
return new KeyIterator();
}
@Override
public int size() {
return ConcurrentReferenceHashMap.this.size();
}
@Override
public boolean isEmpty() {
return ConcurrentReferenceHashMap.this.isEmpty();
}
@Override
public void clear() {
ConcurrentReferenceHashMap.this.clear();
}
@Override
public boolean contains(Object k) {
return ConcurrentReferenceHashMap.this.containsKey(k);
}
@Override
public Spliterator<K> spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.CONCURRENT);
}
}
/**
* Internal key iterator implementation.
*/
private final class KeyIterator implements Iterator<K> {
private final Iterator<Map.Entry<K, V>> iterator = entrySet().iterator();
@Override
public boolean hasNext() {
return this.iterator.hasNext();
}
@Override
public void remove() {
this.iterator.remove();
}
@Override
public K next() {
return this.iterator.next().getKey();
}
}
/**
* Internal values collection implementation.
*/
private final class Values extends AbstractCollection<V> {
@Override
public Iterator<V> iterator() {
return new ValueIterator();
}
@Override
public int size() {
return ConcurrentReferenceHashMap.this.size();
}
@Override
public boolean isEmpty() {
return ConcurrentReferenceHashMap.this.isEmpty();
}
@Override
public void clear() {
ConcurrentReferenceHashMap.this.clear();
}
@Override
public boolean contains(Object v) {
return ConcurrentReferenceHashMap.this.containsValue(v);
}
@Override
public Spliterator<V> spliterator() {
return Spliterators.spliterator(this, Spliterator.CONCURRENT);
}
}
/**
* Internal value iterator implementation.
*/
private final class ValueIterator implements Iterator<V> {
private final Iterator<Map.Entry<K, V>> iterator = entrySet().iterator();
@Override
public boolean hasNext() {
return this.iterator.hasNext();
}
@Override
public void remove() {
this.iterator.remove();
}
@Override
public V next() {
return this.iterator.next().getValue();
}
}
/**
* Internal entry iterator implementation.
*/
private final class EntryIterator implements Iterator<Map.Entry<K, V>> {
private class EntryIterator implements Iterator<Map.Entry<K, V>> {
private int segmentIndex;
@@ -141,32 +141,26 @@ final class UnmodifiableMultiValueMap<K,V> implements MultiValueMap<K,V>, Serial
@Override
public Set<K> keySet() {
Set<K> keySet = this.keySet;
if (keySet == null) {
keySet = Collections.unmodifiableSet(this.delegate.keySet());
this.keySet = keySet;
if (this.keySet == null) {
this.keySet = Collections.unmodifiableSet(this.delegate.keySet());
}
return keySet;
return this.keySet;
}
@Override
public Set<Entry<K, List<V>>> entrySet() {
Set<Entry<K, List<V>>> entrySet = this.entrySet;
if (entrySet == null) {
entrySet = new UnmodifiableEntrySet<>(this.delegate.entrySet());
this.entrySet = entrySet;
if (this.entrySet == null) {
this.entrySet = new UnmodifiableEntrySet<>(this.delegate.entrySet());
}
return entrySet;
return this.entrySet;
}
@Override
public Collection<List<V>> values() {
Collection<List<V>> values = this.values;
if (values == null) {
values = new UnmodifiableValueCollection<>(this.delegate.values());
this.values = values;
if (this.values == null) {
this.values = new UnmodifiableValueCollection<>(this.delegate.values());
}
return values;
return this.values;
}
// unsupported
@@ -16,7 +16,8 @@
package org.springframework.util;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -24,8 +25,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Spliterator;
import java.util.stream.Collectors;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
@@ -33,13 +32,12 @@ import org.junit.jupiter.api.Test;
import org.springframework.util.ConcurrentReferenceHashMap.Entry;
import org.springframework.util.ConcurrentReferenceHashMap.Reference;
import org.springframework.util.ConcurrentReferenceHashMap.Restructure;
import org.springframework.util.comparator.Comparators;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.entry;
/**
* Tests for {@link ConcurrentReferenceHashMap}.
@@ -49,6 +47,8 @@ import static org.assertj.core.api.Assertions.entry;
*/
class ConcurrentReferenceHashMapTests {
private static final Comparator<? super String> NULL_SAFE_STRING_SORT = Comparators.nullsLow();
private TestWeakConcurrentCache<Integer, String> map = new TestWeakConcurrentCache<>();
@@ -450,174 +450,19 @@ class ConcurrentReferenceHashMapTests {
assertThat(this.map.keySet()).isEqualTo(expected);
}
@Test // gh-35817
void keySetContains() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
assertThat(this.map.keySet()).containsExactlyInAnyOrder(123, 456, null);
}
@Test // gh-35817
void keySetRemove() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
assertThat(this.map.keySet().remove(123)).isTrue();
assertThat(this.map).doesNotContainKey(123);
assertThat(this.map.keySet().remove(123)).isFalse();
}
@Test // gh-35817
void keySetIterator() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
Iterator<Integer> it = this.map.keySet().iterator();
assertThat(it).toIterable().containsExactlyInAnyOrder(123, 456, null);
assertThat(it).isExhausted();
}
@Test // gh-35817
void keySetIteratorRemove() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
Iterator<Integer> keySetIterator = this.map.keySet().iterator();
while (keySetIterator.hasNext()) {
Integer key = keySetIterator.next();
if (key != null && key.equals(456)) {
keySetIterator.remove();
}
}
assertThat(this.map).containsOnlyKeys(123, null);
}
@Test // gh-35817
void keySetClear() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
this.map.keySet().clear();
assertThat(this.map).isEmpty();
assertThat(this.map.keySet()).isEmpty();
}
@Test // gh-35817
void keySetAdd() {
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(() -> this.map.keySet().add(12345));
}
@Test // gh-35817
void keySetStream() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
Set<Integer> keys = this.map.keySet().stream().collect(Collectors.toSet());
assertThat(keys).containsExactlyInAnyOrder(123, 456, null);
}
@Test // gh-35817
void keySetSpliteratorCharacteristics() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
Spliterator<Integer> spliterator = this.map.keySet().spliterator();
assertThat(spliterator).hasOnlyCharacteristics(Spliterator.CONCURRENT, Spliterator.DISTINCT);
assertThat(spliterator.estimateSize()).isEqualTo(3L);
assertThat(spliterator.getExactSizeIfKnown()).isEqualTo(-1L);
}
@Test
void valuesCollection() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
assertThat(this.map.values()).containsExactlyInAnyOrder("123", null, "789");
}
@Test // gh-35817
void valuesCollectionAdd() {
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(() -> this.map.values().add("12345"));
}
@Test // gh-35817
void valuesCollectionClear() {
Collection<String> values = this.map.values();
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
assertThat(values).hasSize(3);
values.clear();
assertThat(values).isEmpty();
assertThat(this.map).isEmpty();
}
@Test // gh-35817
void valuesCollectionRemoval() {
Collection<String> values = this.map.values();
assertThat(values).isEmpty();
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
assertThat(values).containsExactlyInAnyOrder("123", null, "789");
values.remove(null);
assertThat(values).containsExactlyInAnyOrder("123", "789");
assertThat(map).containsOnly(entry(123, "123"), entry(null, "789"));
values.remove("123");
values.remove("789");
assertThat(values).isEmpty();
assertThat(map).isEmpty();
}
@Test // gh-35817
void valuesCollectionIterator() {
Iterator<String> iterator = this.map.values().iterator();
assertThat(iterator).isExhausted();
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
iterator = this.map.values().iterator();
assertThat(iterator).toIterable().containsExactlyInAnyOrder("123", null, "789");
}
@Test // gh-35817
void valuesCollectionIteratorRemoval() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
Iterator<String> iterator = this.map.values().iterator();
while (iterator.hasNext()) {
String value = iterator.next();
if (value != null && value.equals("789")) {
iterator.remove();
}
}
assertThat(iterator).isExhausted();
assertThat(this.map.values()).containsExactlyInAnyOrder("123", null);
assertThat(this.map).containsOnlyKeys(123, 456);
}
@Test // gh-35817
void valuesCollectionStream() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
List<String> values = this.map.values().stream().toList();
assertThat(values).containsExactlyInAnyOrder("123", null, "789");
}
@Test // gh-35817
void valuesCollectionSpliteratorCharacteristics() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
Spliterator<String> spliterator = this.map.values().spliterator();
assertThat(spliterator).hasOnlyCharacteristics(Spliterator.CONCURRENT);
assertThat(spliterator.estimateSize()).isEqualTo(3L);
assertThat(spliterator.getExactSizeIfKnown()).isEqualTo(-1L);
List<String> actual = new ArrayList<>(this.map.values());
List<String> expected = new ArrayList<>();
expected.add("123");
expected.add(null);
expected.add("789");
actual.sort(NULL_SAFE_STRING_SORT);
expected.sort(NULL_SAFE_STRING_SORT);
assertThat(actual).isEqualTo(expected);
}
@Test
@@ -696,17 +541,6 @@ class ConcurrentReferenceHashMapTests {
copy.forEach(entry -> assertThat(entrySet).doesNotContain(entry));
}
@Test // gh-35817
void entrySetSpliteratorCharacteristics() {
this.map.put(1, "1");
this.map.put(2, "2");
this.map.put(3, "3");
Spliterator<Map.Entry<Integer, String>> spliterator = this.map.entrySet().spliterator();
assertThat(spliterator).hasOnlyCharacteristics(Spliterator.CONCURRENT, Spliterator.DISTINCT);
assertThat(spliterator.estimateSize()).isEqualTo(3L);
assertThat(spliterator.getExactSizeIfKnown()).isEqualTo(-1L);
}
@Test
void supportNullReference() {
// GC could happen during restructure so we must be able to create a reference for a null entry
@@ -81,7 +81,7 @@ class MapTests extends AbstractExpressionTests {
}
@Test
void inlineMapIsInstanceOfMap() {
void inelineMapIsInstanceOfMap() {
evaluate("{a:1, b:2} instanceof T(java.util.Map)", "true", Boolean.class);
}
@@ -19,6 +19,8 @@ package org.springframework.jdbc.core;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.jspecify.annotations.Nullable;
/**
* Parameterized callback interface used by the {@link JdbcTemplate} class for
* batch updates.
@@ -47,6 +49,6 @@ public interface ParameterizedPreparedStatementSetter<T> {
* @param argument the object containing the values to be set
* @throws SQLException if an SQLException is encountered (i.e. there is no need to catch SQLException)
*/
void setValues(PreparedStatement ps, T argument) throws SQLException;
void setValues(PreparedStatement ps, @Nullable T argument) throws SQLException;
}
@@ -41,8 +41,9 @@ import org.springframework.util.Assert;
* @author Rod Johnson
* @author Juergen Hoeller
* @see #loadBeanDefinitions
* @deprecated in favor of Spring's common bean definition formats and/or
* custom BeanDefinitionReader implementations
* @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
* @deprecated in favor of Spring's common bean definition formats and/or custom
* reader implementations
*/
@Deprecated(since = "5.3")
public class JdbcBeanDefinitionReader {
@@ -24,8 +24,8 @@ import java.sql.ResultSet
* @author Mario Arias
* @since 5.0
*/
inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String): T? =
queryForObject(sql, T::class.java)
inline fun <reified T> JdbcOperations.queryForObject(sql: String): T =
queryForObject(sql, T::class.java as Class<*>) as T
/**
* Extensions for [JdbcOperations.queryForObject] providing a RowMapper-like function
@@ -34,7 +34,7 @@ inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String): T? =
* @author Mario Arias
* @since 5.0
*/
inline fun <reified T> JdbcOperations.queryForObject(sql: String, vararg args: Any?, crossinline function: (ResultSet, Int) -> T): T =
inline fun <reified T> JdbcOperations.queryForObject(sql: String, vararg args: Any, crossinline function: (ResultSet, Int) -> T): T =
queryForObject(sql, { resultSet, i -> function(resultSet, i) }, *args)
/**
@@ -44,18 +44,18 @@ inline fun <reified T> JdbcOperations.queryForObject(sql: String, vararg args: A
* @author Mario Arias
* @since 5.0
*/
inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String, args: Array<out Any?>, argTypes: IntArray): T? =
queryForObject(sql, args, argTypes, T::class.java)
inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<out Any>, argTypes: IntArray): T =
queryForObject(sql, args, argTypes, T::class.java as Class<*>) as T
/**
* Extension for [JdbcOperations.queryForObject] providing a
* `queryForObject<Foo>("...", arg1, argN)` variant.
* `queryForObject<Foo>("...", arrayOf(arg1, argN))` variant.
*
* @author Sébastien Deleuze
* @since 7.0
* @author Mario Arias
* @since 5.0
*/
inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String, vararg args: Any?): T? =
queryForObject(sql, T::class.java, *args)
inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<out Any>): T =
queryForObject(sql, T::class.java as Class<*>, args) as T
/**
* Extension for [JdbcOperations.queryForList] providing a `queryForList<Foo>("...")` variant.
@@ -63,8 +63,9 @@ inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String, vararg a
* @author Mario Arias
* @since 5.0
*/
inline fun <reified T : Any> JdbcOperations.queryForList(sql: String): List<T?> =
queryForList(sql, T::class.java)
@Suppress("UNCHECKED_CAST")
inline fun <reified T> JdbcOperations.queryForList(sql: String): List<T> =
queryForList(sql, T::class.java) as List<T>
/**
* Extension for [JdbcOperations.queryForList] providing a
@@ -73,19 +74,21 @@ inline fun <reified T : Any> JdbcOperations.queryForList(sql: String): List<T?>
* @author Mario Arias
* @since 5.0
*/
inline fun <reified T : Any> JdbcOperations.queryForList(sql: String, args: Array<out Any?>,
argTypes: IntArray): List<T?> =
queryForList(sql, args, argTypes, T::class.java)
@Suppress("UNCHECKED_CAST")
inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out Any>,
argTypes: IntArray): List<T> =
queryForList(sql, args, argTypes, T::class.java) as List<T>
/**
* Extension for [JdbcOperations.queryForList] providing a
* `queryForList<Foo>("...", arg1, argN)` variant.
* `queryForList<Foo>("...", arrayOf(arg1, argN))` variant.
*
* @author Sebastien Deleuze
* @since 7.0
* @author Mario Arias
* @since 5.0
*/
inline fun <reified T : Any> JdbcOperations.queryForList(sql: String, vararg args: Any?): List<T?> =
queryForList(sql, T::class.java, *args)
@Suppress("UNCHECKED_CAST")
inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out Any>): List<T> =
queryForList(sql, T::class.java, args) as List<T>
/**
@@ -95,7 +98,7 @@ inline fun <reified T : Any> JdbcOperations.queryForList(sql: String, vararg arg
* @author Mario Arias
* @since 5.0
*/
fun <T> JdbcOperations.query(sql: String, vararg args: Any?,
fun <T> JdbcOperations.query(sql: String, vararg args: Any,
function: (ResultSet) -> T): T =
query(sql, ResultSetExtractor { function(it) }, *args)
@@ -106,7 +109,7 @@ fun <T> JdbcOperations.query(sql: String, vararg args: Any?,
* @author Mario Arias
* @since 5.0
*/
fun JdbcOperations.query(sql: String, vararg args: Any?, function: (ResultSet) -> Unit): Unit =
fun JdbcOperations.query(sql: String, vararg args: Any, function: (ResultSet) -> Unit): Unit =
query(sql, { function(it) }, *args)
/**
@@ -116,5 +119,5 @@ fun JdbcOperations.query(sql: String, vararg args: Any?, function: (ResultSet) -
* @author Mario Arias
* @since 5.0
*/
fun <T> JdbcOperations.query(sql: String, vararg args: Any?, function: (ResultSet, Int) -> T): List<T> =
fun <T> JdbcOperations.query(sql: String, vararg args: Any, function: (ResultSet, Int) -> T): List<T> =
query(sql, { rs, i -> function(rs, i) }, *args)
@@ -46,7 +46,7 @@ class JdbcOperationsExtensionsTests {
@Test
fun `queryForObject with nullable reified type parameters`() {
every { template.queryForObject(sql, any<Class<Int>>()) } returns null
assertThat(template.queryForObject<Int>(sql)).isNull()
assertThat(template.queryForObject<Int?>(sql)).isNull()
verify { template.queryForObject(sql, any<Class<Int>>()) }
}
@@ -65,17 +65,8 @@ class JdbcOperationsExtensionsTests {
}
@Test
fun `queryForObject with reified type parameters, non-null args array and argTypes`() {
val args = arrayOf(3, 4)
val argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber)
every { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) } returns 2
assertThat(template.queryForObject<Int>(sql, args, argTypes)).isEqualTo(2)
verify { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) }
}
@Test
fun `queryForObject with reified type parameters, nullable args array and argTypes`() {
val args = arrayOf(3, null)
fun `queryForObject with reified type parameters and argTypes`() {
val args = arrayOf(3)
val argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber)
every { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) } returns 2
assertThat(template.queryForObject<Int>(sql, args, argTypes)).isEqualTo(2)
@@ -87,22 +78,24 @@ class JdbcOperationsExtensionsTests {
val args = arrayOf(3)
val argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber)
every { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) } returns null
assertThat(template.queryForObject<Int>(sql, args, argTypes)).isNull()
assertThat(template.queryForObject<Int?>(sql, args, argTypes)).isNull()
verify { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) }
}
@Test
fun `queryForObject with reified type parameters and args`() {
every { template.queryForObject(sql, any<Class<Int>>(), 3, null) } returns 2
assertThat(template.queryForObject<Int>(sql, 3, null)).isEqualTo(2)
verify { template.queryForObject(sql, any<Class<Int>>(), 3, null) }
val args = arrayOf(3, 4)
every { template.queryForObject(sql, any<Class<Int>>(), args) } returns 2
assertThat(template.queryForObject<Int>(sql, args)).isEqualTo(2)
verify { template.queryForObject(sql, any<Class<Int>>(), args) }
}
@Test
fun `queryForObject with nullable reified type parameters and args`() {
every { template.queryForObject(sql, any<Class<Int>>(), 3, 4) } returns null
assertThat(template.queryForObject<Int>(sql, 3, 4)).isNull()
verify { template.queryForObject(sql, any<Class<Int>>(), 3, 4) }
val args = arrayOf(3, 4)
every { template.queryForObject(sql, any<Class<Int>>(), args) } returns null
assertThat(template.queryForObject<Int?>(sql, args)).isNull()
verify { template.queryForObject(sql, any<Class<Int>>(), args) }
}
@Test
@@ -117,12 +110,12 @@ class JdbcOperationsExtensionsTests {
fun `queryForList with nullable reified type parameters`() {
val list = listOf(1, null, 3)
every { template.queryForList(sql, any<Class<Int>>()) } returns list
assertThat(template.queryForList<Int>(sql)).isEqualTo(list)
assertThat(template.queryForList<Int?>(sql)).isEqualTo(list)
verify { template.queryForList(sql, any<Class<Int>>()) }
}
@Test
fun `queryForList with reified type parameters, non-null args and argTypes`() {
fun `queryForList with reified type parameters and argTypes`() {
val list = listOf(1, 2, 3)
val args = arrayOf(3)
val argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber)
@@ -132,49 +125,31 @@ class JdbcOperationsExtensionsTests {
}
@Test
fun `queryForList with reified type parameters, nullable args and argTypes`() {
val list = listOf(1, 2, 3)
val args = arrayOf("foo", null)
val argTypes = intArrayOf(JDBCType.VARCHAR.vendorTypeNumber)
every { template.queryForList(sql, args, argTypes, any<Class<Int>>()) } returns list
assertThat(template.queryForList<Int>(sql, args, argTypes)).isEqualTo(list)
verify { template.queryForList(sql, args, argTypes, any<Class<Int>>()) }
}
@Test
fun `queryForList with nullable reified type parameters, non-null args and argTypes`() {
fun `queryForList with nullable reified type parameters and argTypes`() {
val list = listOf(1, null, 3)
val args = arrayOf(3)
val argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber)
every { template.queryForList(sql, args, argTypes, any<Class<Int>>()) } returns list
assertThat(template.queryForList<Int>(sql, args, argTypes)).isEqualTo(list)
verify { template.queryForList(sql, args, argTypes, any<Class<Int>>()) }
}
@Test
fun `queryForList with nullable reified type parameters, nullable args and argTypes`() {
val list = listOf(1, null, 3)
val args = arrayOf("foo", null)
val argTypes = intArrayOf(JDBCType.VARCHAR.vendorTypeNumber)
every { template.queryForList(sql, args, argTypes, any<Class<Int>>()) } returns list
assertThat(template.queryForList<Int>(sql, args, argTypes)).isEqualTo(list)
assertThat(template.queryForList<Int?>(sql, args, argTypes)).isEqualTo(list)
verify { template.queryForList(sql, args, argTypes, any<Class<Int>>()) }
}
@Test
fun `queryForList with reified type parameters and args`() {
val list = listOf(1, 2, 3)
every { template.queryForList(sql, any<Class<Int>>(), 3, null) } returns list
template.queryForList<Int>(sql, 3, null)
verify { template.queryForList(sql, any<Class<Int>>(), 3, null) }
val args = arrayOf(3, 4)
every { template.queryForList(sql, any<Class<Int>>(), args) } returns list
template.queryForList<Int>(sql, args)
verify { template.queryForList(sql, any<Class<Int>>(), args) }
}
@Test
fun `queryForList with nullable reified type parameters and args`() {
val list = listOf(1, null, 3)
every { template.queryForList(sql, any<Class<Int>>(), 3, null) } returns list
template.queryForList<Int>(sql, 3, null)
verify { template.queryForList(sql, any<Class<Int>>(), 3, null) }
val args = arrayOf(3, 4)
every { template.queryForList(sql, any<Class<Int>>(), args) } returns list
template.queryForList<Int?>(sql, args)
verify { template.queryForList(sql, any<Class<Int>>(), args) }
}
@Test
@@ -105,10 +105,14 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onType(EmployeeLocation.class)
.withMemberCategories(MemberCategory.ACCESS_DECLARED_FIELDS)).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(Car.class, "setId")).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(Car.class, "getId")).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(Car.class, "setModel")).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(Car.class, "getModel")).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "setId")
.invoke()).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "getId")
.invoke()).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "setModel")
.invoke()).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "getModel")
.invoke()).accepts(hints);
});
}
@@ -153,7 +153,7 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
* Returns {@link ExtensionContextScope#TEST_METHOD ExtensionContextScope.TEST_METHOD}.
* <p>This can be effectively overridden by annotating a test class with
* {@code @SpringExtensionConfig(useTestClassScopedExtensionContext = true)}.
* See the {@linkplain SpringExtension class-level Javadoc} for further details.
* See the {@link SpringExtension class-level Javadoc} for further details.
* @since 7.0
* @see SpringExtensionConfig#useTestClassScopedExtensionContext()
*/
@@ -334,10 +334,6 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
* <li>The parameter is of type {@link ApplicationEvents} or a sub-type thereof.</li>
* <li>{@link ParameterResolutionDelegate#isAutowirable} returns {@code true}.</li>
* </ol>
* <p>This method does not {@linkplain #getApplicationContext(ExtensionContext)
* load} the {@code ApplicationContext} or verify that the application context
* actually contains a matching candidate bean, since doing so would potentially
* load an application context too early or unnecessarily.
* <p><strong>WARNING</strong>: If a test class {@code Constructor} is annotated
* with {@code @Autowired} or automatically autowirable (see
* {@link org.springframework.test.context.TestConstructor @TestConstructor}),
@@ -395,18 +391,7 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
/**
* Get the {@link ApplicationContext} associated with the supplied {@link ExtensionContext}.
* <p><strong>NOTE</strong>: As of Spring Framework 7.0, the supplied
* {@code ExtensionContext} may not be properly <em>scoped</em>. See the
* {@linkplain SpringExtension class-level Javadoc} for further details.
* <p><strong>WARNING</strong>: Invoking this method ensures that the
* corresponding {@code ApplicationContext} is
* {@linkplain org.springframework.test.context.TestContext#getApplicationContext()
* loaded}. Consequently, this method should not be used if eager loading of
* the application context is undesired. For example,
* {@link #supportsParameter(ParameterContext, ExtensionContext)} intentionally
* does not invoke this method, since doing so would potentially load an
* application context too early or unnecessarily.
* Get the {@link ApplicationContext} associated with the supplied {@code ExtensionContext}.
* @param context the current {@code ExtensionContext} (never {@code null})
* @return the application context
* @throws IllegalStateException if an error occurs while retrieving the application context
@@ -417,7 +402,7 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
}
/**
* Get the {@link TestContextManager} associated with the supplied {@link ExtensionContext}.
* Get the {@link TestContextManager} associated with the supplied {@code ExtensionContext}.
* @return the {@code TestContextManager} (never {@code null})
*/
static TestContextManager getTestContextManager(ExtensionContext context) {
@@ -28,13 +28,9 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Function;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.TypeRef;
import com.jayway.jsonpath.spi.mapper.MappingProvider;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
@@ -148,10 +144,9 @@ class DefaultRestTestClient implements RestTestClient {
client.mutate()
.configureMessageConverters(convertersBuilder -> {
HttpMessageConverters converters = convertersBuilder.build();
if (converters.isEmpty()) {
converters = HttpMessageConverters.forClient().registerDefaults().build();
if (converters.iterator().hasNext()) {
this.converter = JsonConverterDelegate.of(converters);
}
this.converter = JsonConverterDelegate.of(converters);
})
.build();
}
@@ -490,8 +485,7 @@ class DefaultRestTestClient implements RestTestClient {
@Override
public JsonPathAssertions jsonPath(String expression) {
Configuration config = JsonPathConfigurationProvider.getConfiguration(this.result);
return new JsonPathAssertions(this, getBodyAsString(), expression, config);
return new JsonPathAssertions(this, getBodyAsString(), expression, null);
}
@Override
@@ -545,37 +539,4 @@ class DefaultRestTestClient implements RestTestClient {
}
}
private static class JsonPathConfigurationProvider {
static Configuration getConfiguration(EntityExchangeResult<?> result) {
Configuration config = Configuration.defaultConfiguration();
JsonConverterDelegate delegate = result.getJsonConverterDelegate();
return (delegate != null ? config.mappingProvider(new MessageConverterMappingProvider(delegate)) : config);
}
}
private record MessageConverterMappingProvider(JsonConverterDelegate delegate) implements MappingProvider {
@Override
public <T> T map(Object value, Class<T> targetType, Configuration configuration) {
return mapToTargetType(value, ResolvableType.forClass(targetType));
}
@Override
public <T> T map(Object value, TypeRef<T> targetType, Configuration configuration) {
return mapToTargetType(value, ResolvableType.forType(targetType.getType()));
}
private <T> T mapToTargetType(Object value, ResolvableType targetType) {
try {
return delegate().map(value, targetType);
}
catch (IOException ex) {
throw new IllegalStateException("Failed to map " + value + " to " + targetType, ex);
}
}
}
}
@@ -23,7 +23,6 @@ import java.util.Map;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.web.Person;
@@ -107,19 +106,6 @@ class JsonPathAssertionTests {
.jsonPath("$.performers[1].name").value(v -> MatcherAssert.assertThat(v, equalTo("Yehudi Menuhin")));
}
@Test
void valueConsumer() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$.composers[0].name").value(
String.class,
name -> assertThat(name).isEqualTo("Johann Sebastian Bach"))
.jsonPath("$.composers[0].name").value(
ParameterizedTypeReference.forType(String.class),
name -> assertThat(name).isEqualTo("Johann Sebastian Bach"));
}
@Test
void hamcrestMatcher() {
client.get().uri("/music/people")
@@ -26,7 +26,7 @@ import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.Aware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.MethodClassKey;
import org.springframework.util.ClassUtils;
@@ -163,8 +163,8 @@ public abstract class AbstractFallbackTransactionAttributeSource
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Skip setBeanFactory method on BeanFactoryAware.
if (method.getDeclaringClass() == BeanFactoryAware.class) {
// Skip methods declared on BeanFactoryAware and co.
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
return null;
}
@@ -32,7 +32,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.testfixture.io.SerializationTestUtils;
@@ -60,7 +59,6 @@ class AnnotationTransactionAttributeSourceTests {
private final AnnotationTransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource();
@Test
void serializable() throws Exception {
TestBean1 tb = new TestBean1();
@@ -125,10 +123,6 @@ class AnnotationTransactionAttributeSourceTests {
void transactionAttributeDeclaredOnInterfaceMethodOnly() {
TransactionAttribute actual = getTransactionAttribute(TestBean2.class, ITestBean2.class, "getAge");
assertThat(actual).satisfies(hasNoRollbackRule());
actual = getTransactionAttribute(TestBean2.class, ITestBean2X.class, "getAge");
assertThat(actual).satisfies(hasNoRollbackRule());
actual = getTransactionAttribute(ITestBean2X.class, ITestBean2X.class, "getAge");
assertThat(actual).satisfies(hasNoRollbackRule());
}
/**
@@ -255,7 +249,6 @@ class AnnotationTransactionAttributeSourceTests {
assertThat(actual.isReadOnly()).isTrue();
}
@Nested
class JtaAttributeTests {
@@ -283,7 +276,6 @@ class AnnotationTransactionAttributeSourceTests {
assertThat(getNameAttr.getPropagationBehavior()).isEqualTo(TransactionAttribute.PROPAGATION_SUPPORTS);
}
static class JtaAnnotatedBean1 implements ITestBean1 {
private String name;
@@ -313,6 +305,7 @@ class AnnotationTransactionAttributeSourceTests {
}
}
@jakarta.transaction.Transactional(jakarta.transaction.Transactional.TxType.SUPPORTS)
static class JtaAnnotatedBean2 implements ITestBean1 {
@@ -369,6 +362,7 @@ class AnnotationTransactionAttributeSourceTests {
}
}
@jakarta.transaction.Transactional(jakarta.transaction.Transactional.TxType.SUPPORTS)
interface ITestJta {
@@ -381,8 +375,8 @@ class AnnotationTransactionAttributeSourceTests {
void setName(String name);
}
}
}
@Nested
class Ejb3AttributeTests {
@@ -454,6 +448,7 @@ class AnnotationTransactionAttributeSourceTests {
}
}
@jakarta.ejb.TransactionAttribute(TransactionAttributeType.SUPPORTS)
static class Ejb3AnnotatedBean2 implements ITestBean1 {
@@ -511,7 +506,6 @@ class AnnotationTransactionAttributeSourceTests {
}
}
@Nested
class GroovyTests {
@@ -525,7 +519,6 @@ class AnnotationTransactionAttributeSourceTests {
assertThat(attributeSource.getTransactionAttribute(getMetaClassMethod, GroovyTestBean.class)).isNull();
}
@Transactional
static class GroovyTestBean implements ITestBean1, GroovyObject {
@@ -578,7 +571,6 @@ class AnnotationTransactionAttributeSourceTests {
}
}
private Consumer<TransactionAttribute> hasRollbackRules(RollbackRuleAttribute... rollbackRuleAttributes) {
return transactionAttribute -> {
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
@@ -634,12 +626,7 @@ class AnnotationTransactionAttributeSourceTests {
}
interface ITestBean2X extends ITestBean2, BeanNameAware {
@Transactional
int getAge();
void setAge(int age);
interface ITestBean2X extends ITestBean2 {
String getName();
@@ -748,10 +735,6 @@ class AnnotationTransactionAttributeSourceTests {
this.age = age;
}
@Override
public void setBeanName(String name) {
}
@Override
public String getName() {
return name;
@@ -934,7 +917,6 @@ class AnnotationTransactionAttributeSourceTests {
}
}
@Transactional(label = {"retryable", "long-running"})
static class TestBean11 {
@@ -57,17 +57,10 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
private final List<HttpMessageConverter<?>> messageConverters;
DefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
@Override
public boolean isEmpty() {
return this.messageConverters.isEmpty();
}
@Override
public Iterator<HttpMessageConverter<?>> iterator() {
return this.messageConverters.iterator();
@@ -32,11 +32,6 @@ import java.util.function.Consumer;
*/
public interface HttpMessageConverters extends Iterable<HttpMessageConverter<?>> {
/**
* Return true if this instance does not contain any message converters.
*/
boolean isEmpty();
/**
* Create a builder instance, tailored for HTTP client usage.
* <p>The following HTTP message converters can be detected and registered if available, in order:
@@ -25,11 +25,12 @@ import org.springframework.http.ProblemDetail;
import org.springframework.web.ErrorResponse;
/**
* Fatal binding exception, thrown when we want to treat binding exceptions as
* unrecoverable.
* Fatal binding exception, thrown when we want to
* treat binding exceptions as unrecoverable.
*
* <p>Extends {@link ServletException} for convenient throwing in any Servlet
* resource (such as a Filter).
* <p>Extends ServletException for convenient throwing in any Servlet resource
* (such as a Filter), and NestedServletException for proper root cause handling
* (as the plain ServletException doesn't expose its root cause at all).
*
* @author Rod Johnson
* @author Juergen Hoeller
@@ -37,12 +38,12 @@ import org.springframework.web.ErrorResponse;
@SuppressWarnings("serial")
public class ServletRequestBindingException extends ServletException implements ErrorResponse {
private final ProblemDetail body = ProblemDetail.forStatus(getStatusCode());
private final String messageDetailCode;
private final Object @Nullable [] messageDetailArguments;
private @Nullable ProblemDetail body;
/**
* Constructor with a message only.
@@ -94,7 +95,6 @@ public class ServletRequestBindingException extends ServletException implements
this.messageDetailArguments = messageDetailArguments;
}
private String initMessageDetailCode(@Nullable String messageDetailCode) {
return (messageDetailCode != null ?
messageDetailCode : ErrorResponse.getDefaultDetailMessageCode(getClass(), null));
@@ -107,10 +107,7 @@ public class ServletRequestBindingException extends ServletException implements
}
@Override
public synchronized ProblemDetail getBody() {
if (this.body == null) {
this.body = ProblemDetail.forStatus(getStatusCode());
}
public ProblemDetail getBody() {
return this.body;
}
@@ -124,10 +124,7 @@ class HtmlCharacterEntityDecoder {
int value = (!isHexNumberedReference ?
Integer.parseInt(getReferenceSubstring(2)) :
Integer.parseInt(getReferenceSubstring(3), 16));
if (value > Character.MAX_CODE_POINT) {
return false;
}
this.decodedMessage.appendCodePoint(value);
this.decodedMessage.append((char) value);
return true;
}
catch (NumberFormatException ex) {
@@ -21,7 +21,9 @@ import org.springframework.http.HttpEntity
import org.springframework.http.HttpMethod
import org.springframework.http.RequestEntity
import org.springframework.http.ResponseEntity
import java.lang.Class
import java.net.URI
import kotlin.reflect.KClass
/**
* Extension for [RestOperations.getForObject] providing a `getForObject<Foo>(...)`
@@ -34,8 +36,8 @@ import java.net.URI
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> RestOperations.getForObject(url: String, vararg uriVariables: Any?): T? =
getForObject(url, T::class.java, *uriVariables)
inline fun <reified T> RestOperations.getForObject(url: String, vararg uriVariables: Any?): T =
getForObject(url, T::class.java as Class<*>, *uriVariables) as T
/**
* Extension for [RestOperations.getForObject] providing a `getForObject<Foo>(...)`
@@ -48,8 +50,8 @@ inline fun <reified T : Any> RestOperations.getForObject(url: String, vararg uri
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> RestOperations.getForObject(url: String, uriVariables: Map<String, Any?>): T? =
getForObject(url, T::class.java, uriVariables)
inline fun <reified T> RestOperations.getForObject(url: String, uriVariables: Map<String, Any?>): T =
getForObject(url, T::class.java as Class<*>, uriVariables) as T
/**
* Extension for [RestOperations.getForObject] providing a `getForObject<Foo>(...)`
@@ -62,8 +64,8 @@ inline fun <reified T : Any> RestOperations.getForObject(url: String, uriVariabl
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> RestOperations.getForObject(url: URI): T? =
getForObject(url, T::class.java)
inline fun <reified T> RestOperations.getForObject(url: URI): T =
getForObject(url, T::class.java as Class<*>) as T
/**
* Extension for [RestOperations.getForEntity] providing a `getForEntity<Foo>(...)`
@@ -116,9 +118,9 @@ inline fun <reified T: Any> RestOperations.getForEntity(url: String, uriVariable
* @since 5.0.2
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> RestOperations.patchForObject(url: String, request: Any? = null,
vararg uriVariables: Any?): T? =
patchForObject(url, request, T::class.java, *uriVariables)
inline fun <reified T> RestOperations.patchForObject(url: String, request: Any? = null,
vararg uriVariables: Any?): T =
patchForObject(url, request, T::class.java as Class<*>, *uriVariables) as T
/**
* Extension for [RestOperations.patchForObject] providing a `patchForObject<Foo>(...)`
@@ -130,9 +132,9 @@ inline fun <reified T : Any> RestOperations.patchForObject(url: String, request:
* @since 5.0.2
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> RestOperations.patchForObject(url: String, request: Any? = null,
uriVariables: Map<String, *>): T? =
patchForObject(url, request, T::class.java, uriVariables)
inline fun <reified T> RestOperations.patchForObject(url: String, request: Any? = null,
uriVariables: Map<String, *>): T =
patchForObject(url, request, T::class.java as Class<*>, uriVariables) as T
/**
* Extension for [RestOperations.patchForObject] providing a `patchForObject<Foo>(...)`
@@ -144,8 +146,8 @@ inline fun <reified T : Any> RestOperations.patchForObject(url: String, request:
* @since 5.0.2
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> RestOperations.patchForObject(url: URI, request: Any? = null): T? =
patchForObject(url, request, T::class.java)
inline fun <reified T> RestOperations.patchForObject(url: URI, request: Any? = null): T =
patchForObject(url, request, T::class.java as Class<*>) as T
/**
* Extension for [RestOperations.postForObject] providing a `postForObject<Foo>(...)`
@@ -158,9 +160,9 @@ inline fun <reified T : Any> RestOperations.patchForObject(url: URI, request: An
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> RestOperations.postForObject(url: String, request: Any? = null,
vararg uriVariables: Any?): T? =
postForObject(url, request, T::class.java, *uriVariables)
inline fun <reified T> RestOperations.postForObject(url: String, request: Any? = null,
vararg uriVariables: Any?): T =
postForObject(url, request, T::class.java as Class<*>, *uriVariables) as T
/**
* Extension for [RestOperations.postForObject] providing a `postForObject<Foo>(...)`
@@ -173,9 +175,9 @@ inline fun <reified T : Any> RestOperations.postForObject(url: String, request:
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> RestOperations.postForObject(url: String, request: Any? = null,
uriVariables: Map<String, *>): T? =
postForObject(url, request, T::class.java, uriVariables)
inline fun <reified T> RestOperations.postForObject(url: String, request: Any? = null,
uriVariables: Map<String, *>): T =
postForObject(url, request, T::class.java as Class<*>, uriVariables) as T
/**
* Extension for [RestOperations.postForObject] providing a `postForObject<Foo>(...)`
@@ -188,8 +190,8 @@ inline fun <reified T : Any> RestOperations.postForObject(url: String, request:
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T : Any> RestOperations.postForObject(url: URI, request: Any? = null): T? =
postForObject(url, request, T::class.java)
inline fun <reified T> RestOperations.postForObject(url: URI, request: Any? = null): T =
postForObject(url, request, T::class.java as Class<*>) as T
/**
* Extension for [RestOperations.postForEntity] providing a `postForEntity<Foo>(...)`
@@ -78,6 +78,7 @@ class ErrorResponseExceptionTests {
@Test
void httpMediaTypeNotSupportedException() {
List<MediaType> mediaTypes =
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
@@ -95,6 +96,7 @@ class ErrorResponseExceptionTests {
@Test
void httpMediaTypeNotSupportedExceptionWithParseError() {
ErrorResponse ex = new HttpMediaTypeNotSupportedException(
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
@@ -107,6 +109,7 @@ class ErrorResponseExceptionTests {
@Test
void httpMediaTypeNotAcceptableException() {
List<MediaType> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
HttpMediaTypeNotAcceptableException ex = new HttpMediaTypeNotAcceptableException(mediaTypes);
@@ -120,6 +123,7 @@ class ErrorResponseExceptionTests {
@Test
void httpMediaTypeNotAcceptableExceptionWithParseError() {
ErrorResponse ex = new HttpMediaTypeNotAcceptableException(
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
@@ -132,6 +136,7 @@ class ErrorResponseExceptionTests {
@Test
void asyncRequestTimeoutException() {
ErrorResponse ex = new AsyncRequestTimeoutException();
assertDetailMessageCode(ex, null, null);
@@ -143,6 +148,7 @@ class ErrorResponseExceptionTests {
@Test
void httpRequestMethodNotSupportedException() {
HttpRequestMethodNotSupportedException ex =
new HttpRequestMethodNotSupportedException("PUT", Arrays.asList("GET", "POST"));
@@ -156,6 +162,7 @@ class ErrorResponseExceptionTests {
@Test
void missingRequestHeaderException() {
MissingRequestHeaderException ex = new MissingRequestHeaderException("Authorization", this.methodParameter);
assertStatus(ex, HttpStatus.BAD_REQUEST);
@@ -167,6 +174,7 @@ class ErrorResponseExceptionTests {
@Test
void missingServletRequestParameterException() {
MissingServletRequestParameterException ex = new MissingServletRequestParameterException("query", "String");
assertStatus(ex, HttpStatus.BAD_REQUEST);
@@ -178,8 +186,10 @@ class ErrorResponseExceptionTests {
@Test
void missingMatrixVariableException() {
MissingMatrixVariableException ex = new MissingMatrixVariableException("region", this.methodParameter);
assertStatus(ex, HttpStatus.BAD_REQUEST);
assertDetail(ex, "Required path parameter 'region' is not present.");
assertDetailMessageCode(ex, null, new Object[] {ex.getVariableName()});
@@ -189,6 +199,7 @@ class ErrorResponseExceptionTests {
@Test
void missingPathVariableException() {
MissingPathVariableException ex = new MissingPathVariableException("id", this.methodParameter);
assertStatus(ex, HttpStatus.INTERNAL_SERVER_ERROR);
@@ -198,19 +209,9 @@ class ErrorResponseExceptionTests {
assertThat(ex.getHeaders().isEmpty()).isTrue();
}
@Test
void missingPathVariableExceptionAfterConversion() {
MissingPathVariableException ex = new MissingPathVariableException("id", this.methodParameter, true);
assertStatus(ex, HttpStatus.BAD_REQUEST);
assertDetail(ex, "Required path variable 'id' is not present.");
assertDetailMessageCode(ex, null, new Object[] {ex.getVariableName()});
assertThat(ex.getHeaders().isEmpty()).isTrue();
}
@Test
void missingRequestCookieException() {
MissingRequestCookieException ex = new MissingRequestCookieException("oreo", this.methodParameter);
assertStatus(ex, HttpStatus.BAD_REQUEST);
@@ -222,6 +223,7 @@ class ErrorResponseExceptionTests {
@Test
void unsatisfiedServletRequestParameterException() {
UnsatisfiedServletRequestParameterException ex = new UnsatisfiedServletRequestParameterException(
new String[] { "foo=bar", "bar=baz" }, Collections.singletonMap("q", new String[] {"1"}));
@@ -234,6 +236,7 @@ class ErrorResponseExceptionTests {
@Test
void missingServletRequestPartException() {
MissingServletRequestPartException ex = new MissingServletRequestPartException("file");
assertStatus(ex, HttpStatus.BAD_REQUEST);
@@ -245,6 +248,7 @@ class ErrorResponseExceptionTests {
@Test
void methodArgumentNotValidException() {
ValidationTestHelper testHelper = new ValidationTestHelper(MethodArgumentNotValidException.class);
BindingResult result = testHelper.bindingResult();
@@ -276,6 +280,7 @@ class ErrorResponseExceptionTests {
@Test
void unsupportedMediaTypeStatusException() {
List<MediaType> mediaTypes =
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
@@ -293,6 +298,7 @@ class ErrorResponseExceptionTests {
@Test
void unsupportedMediaTypeStatusExceptionWithParseError() {
ErrorResponse ex = new UnsupportedMediaTypeStatusException(
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
@@ -305,6 +311,7 @@ class ErrorResponseExceptionTests {
@Test
void notAcceptableStatusException() {
List<MediaType> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
NotAcceptableStatusException ex = new NotAcceptableStatusException(mediaTypes);
@@ -318,6 +325,7 @@ class ErrorResponseExceptionTests {
@Test
void notAcceptableStatusExceptionWithParseError() {
ErrorResponse ex = new NotAcceptableStatusException(
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
@@ -330,6 +338,7 @@ class ErrorResponseExceptionTests {
@Test
void serverErrorException() {
ServerErrorException ex = new ServerErrorException("Failure", null);
assertStatus(ex, HttpStatus.INTERNAL_SERVER_ERROR);
@@ -341,6 +350,7 @@ class ErrorResponseExceptionTests {
@Test
void missingRequestValueException() {
MissingRequestValueException ex =
new MissingRequestValueException("foo", String.class, "header", this.methodParameter);
@@ -353,6 +363,7 @@ class ErrorResponseExceptionTests {
@Test
void unsatisfiedRequestParameterException() {
UnsatisfiedRequestParameterException ex =
new UnsatisfiedRequestParameterException(
Arrays.asList("foo=bar", "bar=baz"),
@@ -367,6 +378,7 @@ class ErrorResponseExceptionTests {
@Test
void webExchangeBindException() {
ValidationTestHelper testHelper = new ValidationTestHelper(WebExchangeBindException.class);
BindingResult result = testHelper.bindingResult();
@@ -381,6 +393,7 @@ class ErrorResponseExceptionTests {
@Test
void methodNotAllowedException() {
List<HttpMethod> supportedMethods = Arrays.asList(HttpMethod.GET, HttpMethod.POST);
MethodNotAllowedException ex = new MethodNotAllowedException(HttpMethod.PUT, supportedMethods);
@@ -394,6 +407,7 @@ class ErrorResponseExceptionTests {
@Test
void methodNotAllowedExceptionWithoutSupportedMethods() {
MethodNotAllowedException ex = new MethodNotAllowedException(HttpMethod.PUT, Collections.emptyList());
assertStatus(ex, HttpStatus.METHOD_NOT_ALLOWED);
@@ -403,8 +417,9 @@ class ErrorResponseExceptionTests {
assertThat(ex.getHeaders().isEmpty()).isTrue();
}
@Test // gh-30300
@Test // gh-30300
void responseStatusException() {
Locale locale = Locale.UK;
LocaleContextHolder.setLocale(locale);
@@ -504,6 +519,7 @@ class ErrorResponseExceptionTests {
assertThat(BindErrorUtils.resolve(errors, this.messageSource, Locale.UK)).hasSize(4)
.containsValues("Bean A message", "Bean B message", "name is required", "age is below minimum");
}
}
}
@@ -21,8 +21,6 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HtmlUtils}.
*
* @author Alef Arendsen
* @author Martin Kersten
* @author Rick Evans
@@ -30,7 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class HtmlUtilsTests {
@Test
void htmlEscape() {
void testHtmlEscape() {
String unescaped = "\"This is a quote'";
String escaped = HtmlUtils.htmlEscape(unescaped);
assertThat(escaped).isEqualTo("&quot;This is a quote&#39;");
@@ -41,7 +39,14 @@ class HtmlUtilsTests {
}
@Test
void htmlEscapeIntoHtmlCharacterSet() {
void testHtmlUnescape() {
String escaped = "&quot;This is a quote&#39;";
String unescaped = HtmlUtils.htmlUnescape(escaped);
assertThat(unescaped).isEqualTo("\"This is a quote'");
}
@Test
void testEncodeIntoHtmlCharacterSet() {
assertThat(HtmlUtils.htmlEscape("")).as("An empty string should be converted to an empty string").isEmpty();
assertThat(HtmlUtils.htmlEscape("A sentence containing no special characters.")).as("A string containing no special characters should not be affected").isEqualTo("A sentence containing no special characters.");
@@ -55,11 +60,12 @@ class HtmlUtilsTests {
assertThat(HtmlUtils.htmlEscapeDecimal("" + (char) 977)).as("The special character 977 should be encoded to '&#977;'").isEqualTo("&#977;");
}
@Test // SPR-9293
void htmlEscapeIntoHtmlCharacterSetFromUtf8() {
// SPR-9293
@Test
void testEncodeIntoHtmlCharacterSetFromUtf8() {
String utf8 = ("UTF-8");
assertThat(HtmlUtils.htmlEscape("", utf8)).as("An empty string should be converted to an empty string").isEmpty();
assertThat(HtmlUtils.htmlEscape("", utf8)).as("An empty string should be converted to an empty string")
.isEmpty();
assertThat(HtmlUtils.htmlEscape("A sentence containing no special characters.")).as("A string containing no special characters should not be affected").isEqualTo("A sentence containing no special characters.");
assertThat(HtmlUtils.htmlEscape("< >", utf8)).as("'< >' should be encoded to '&lt; &gt;'").isEqualTo("&lt; &gt;");
@@ -69,38 +75,7 @@ class HtmlUtilsTests {
}
@Test
void htmlUnescape() {
String escaped = "&quot;This is a quote&#39;";
String unescaped = HtmlUtils.htmlUnescape(escaped);
assertThat(unescaped).isEqualTo("\"This is a quote'");
}
@Test
void htmlUnescapeHandlesSupplementaryCharactersAsDecimal() {
String expectedCharacter = "😀";
String decimalEntity = "&#128512;";
String actualResultFromDecimal = HtmlUtils.htmlUnescape(decimalEntity);
assertThat(actualResultFromDecimal).as("Decimal entity was not converted correctly.").isEqualTo(expectedCharacter);
}
@Test
void htmlUnescapeHandlesSupplementaryCharactersAsHexadecimal() {
String expectedCharacter = "😀";
String hexEntity = "&#x1F600;";
String actualResultFromHex = HtmlUtils.htmlUnescape(hexEntity);
assertThat(actualResultFromHex).as("Hexadecimal entity was not converted correctly.").isEqualTo(expectedCharacter);
}
@Test
void htmlUnescapeHandlesBasicEntities() {
String input = "&lt;p&gt;Tom &amp; Jerry&#39;s &quot;Show&quot;&lt;/p&gt;";
String expectedOutput = "<p>Tom & Jerry's \"Show\"</p>";
String actualOutput = HtmlUtils.htmlUnescape(input);
assertThat(actualOutput).as("Basic HTML entities were not unescaped correctly.").isEqualTo(expectedOutput);
}
@Test
void htmlUnescapeFromHtmlCharacterSet() {
void testDecodeFromHtmlCharacterSet() {
assertThat(HtmlUtils.htmlUnescape("")).as("An empty string should be converted to an empty string").isEmpty();
assertThat(HtmlUtils.htmlUnescape("This is a sentence containing no special characters.")).as("A string containing no special characters should not be affected").isEqualTo("This is a sentence containing no special characters.");
@@ -270,6 +270,7 @@ class RestOperationsExtensionsTests {
}
@Test
@Disabled("May require Kotlin 2") // TODO Enable after Kotlin 2 upgrade
fun `RestOperations are available`() {
val extensions = Class.forName("org.springframework.web.client.RestOperationsExtensionsKt")
ReflectionUtils.doWithMethods(RestOperations::class.java) { method ->
@@ -280,7 +281,7 @@ class RestOperationsExtensionsTests {
assertThat(f.typeParameters.size).isEqualTo(1)
val type = f.typeParameters[0].upperBounds.first()
assertThat(type.classifier).isEqualTo(Any::class)
assertThat(type.isMarkedNullable).isFalse()
assertThat(type.isMarkedNullable).isTrue()
}
}
}
@@ -113,7 +113,6 @@
<suppress files="PatternParseException" checks="JavadocVariable"/>
<suppress files="web[\\/]reactive[\\/]socket[\\/]CloseStatus" checks="JavadocStyle"/>
<suppress files="RestClientResponseException" checks="MutableException"/>
<suppress files="ServletRequestBindingException" checks="MutableException"/>
<!-- spring-webflux -->
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]web[\\/]reactive[\\/]resource[\\/]GzipSupport" checks="IllegalImport" id="bannedJUnitJupiterImports"/>