mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ab9f7adbc | |||
| 59025c5b96 | |||
| e5de8b9bc6 | |||
| e146e809e5 | |||
| ff62e7355f | |||
| 4e97013595 | |||
| bd10b7ae06 | |||
| e4288170c8 | |||
| 8545a759a7 | |||
| f94645de17 | |||
| 8553f97df1 | |||
| 030dace2be | |||
| 5af1c9b487 | |||
| f3ed04c9d7 | |||
| 46ee944acc | |||
| 6be1c29fda | |||
| 3fa56db88b |
@@ -36,4 +36,4 @@ runtime:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.20/ui-bundle.zip
|
||||
|
||||
@@ -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#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.
|
||||
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.
|
||||
|
||||
[[mockmvc-server-htmlunit-geb-setup]]
|
||||
== MockMvc and Geb Setup
|
||||
@@ -28,7 +28,8 @@ def setup() {
|
||||
----
|
||||
|
||||
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
|
||||
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
|
||||
usage, see
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-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
|
||||
@@ -62,10 +63,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#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:
|
||||
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:
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
|
||||
@@ -7,8 +7,7 @@ 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:
|
||||
@@ -45,7 +44,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage,
|
||||
see xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
|
||||
see <<mockmvc-server-htmlunit-mah-advanced-builder>>.
|
||||
|
||||
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
|
||||
@@ -77,7 +76,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: The default context path is `""`. Alternatively, we can specify the context path,
|
||||
as described in xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
|
||||
as described in <<mockmvc-server-htmlunit-mah-advanced-builder>>.
|
||||
|
||||
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:
|
||||
@@ -144,10 +143,10 @@ Kotlin::
|
||||
======
|
||||
|
||||
The preceding code improves on our
|
||||
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.
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
@@ -203,7 +203,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
|
||||
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
|
||||
usage, see <<mockmvc-server-htmlunit-webdriver-advanced-builder>>.
|
||||
|
||||
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,10 +259,11 @@ Kotlin::
|
||||
======
|
||||
--
|
||||
|
||||
This improves on the design of our xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test]
|
||||
This improves on the design of our
|
||||
xref:testing/mockmvc/htmlunit/mah.adoc#mockmvc-server-htmlunit-mah-usage[HtmlUnit test]
|
||||
by leveraging the Page Object Pattern. As we mentioned in
|
||||
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
|
||||
<<mockmvc-server-htmlunit-webdriver-why>>, we can use the Page Object Pattern with
|
||||
HtmlUnit, but it is much easier with WebDriver. Consider the following
|
||||
`CreateMessagePage` implementation:
|
||||
|
||||
--
|
||||
|
||||
@@ -60,7 +60,7 @@ assume our form looks like the following snippet:
|
||||
</form>
|
||||
----
|
||||
|
||||
How do we ensure that our form produce the correct request to create a new message? A
|
||||
How do we ensure that our form produces 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 earlier, we could perform end-to-end integration testing,
|
||||
To resolve the issues mentioned above, 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 such items as auto-generated IDs, timestamps, and others can
|
||||
* Performing assertions on items such 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
@@ -1,4 +1,4 @@
|
||||
version=6.2.14-SNAPSHOT
|
||||
version=6.2.14
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
-1
@@ -32,7 +32,6 @@ 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 {
|
||||
|
||||
-1
@@ -43,7 +43,6 @@ 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 {
|
||||
|
||||
|
||||
+3
-3
@@ -74,10 +74,10 @@ import org.springframework.util.StringUtils;
|
||||
* @author Rob Harrop
|
||||
* @since 26.11.2003
|
||||
* @see DefaultListableBeanFactory
|
||||
* @deprecated as of 5.3, in favor of Spring's common bean definition formats
|
||||
* and/or custom reader implementations
|
||||
* @deprecated in favor of Spring's common bean definition formats and/or
|
||||
* custom BeanDefinitionReader implementations
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(since = "5.3")
|
||||
public class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader {
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+37
-20
@@ -77,7 +77,7 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
|
||||
private boolean allowNullValues = true;
|
||||
|
||||
private boolean dynamic = true;
|
||||
private volatile boolean dynamic = true;
|
||||
|
||||
private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
|
||||
|
||||
@@ -102,10 +102,15 @@ 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>Calling this with a {@code null} collection argument resets the
|
||||
* mode to 'dynamic', allowing for further creation of caches again.
|
||||
* <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()
|
||||
*/
|
||||
public void setCacheNames(@Nullable Collection<String> cacheNames) {
|
||||
if (cacheNames != null) {
|
||||
@@ -245,11 +250,6 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
return Collections.unmodifiableSet(this.cacheMap.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Cache getCache(String name) {
|
||||
@@ -260,6 +260,33 @@ 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,
|
||||
@@ -303,16 +330,6 @@ 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}
|
||||
|
||||
+3
-3
@@ -25,7 +25,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.Aware;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -98,8 +98,8 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
|
||||
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
|
||||
return null;
|
||||
}
|
||||
// Skip methods declared on BeanFactoryAware and co.
|
||||
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
|
||||
// Skip setBeanFactory method on BeanFactoryAware.
|
||||
if (method.getDeclaringClass() == BeanFactoryAware.class) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+22
-2
@@ -24,7 +24,6 @@ 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;
|
||||
@@ -42,7 +41,7 @@ class CaffeineCacheManagerTests {
|
||||
@Test
|
||||
@SuppressWarnings("cast")
|
||||
void dynamicMode() {
|
||||
CacheManager cm = new CaffeineCacheManager();
|
||||
CaffeineCacheManager cm = new CaffeineCacheManager();
|
||||
|
||||
Cache cache1 = cm.getCache("c1");
|
||||
assertThat(cache1).isInstanceOf(CaffeineCache.class);
|
||||
@@ -76,6 +75,14 @@ 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
|
||||
@@ -131,11 +138,24 @@ 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
|
||||
|
||||
Vendored
+27
-10
@@ -54,7 +54,7 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
|
||||
|
||||
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
|
||||
|
||||
private boolean dynamic = true;
|
||||
private volatile boolean dynamic = true;
|
||||
|
||||
private boolean allowNullValues = true;
|
||||
|
||||
@@ -82,10 +82,15 @@ 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>Calling this with a {@code null} collection argument resets the
|
||||
* mode to 'dynamic', allowing for further creation of caches again.
|
||||
* <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()
|
||||
*/
|
||||
public void setCacheNames(@Nullable Collection<String> cacheNames) {
|
||||
if (cacheNames != null) {
|
||||
@@ -160,11 +165,6 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
return Collections.unmodifiableSet(this.cacheMap.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Cache getCache(String name) {
|
||||
@@ -175,6 +175,23 @@ 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
|
||||
|
||||
+3
-3
@@ -27,7 +27,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.Aware;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -140,8 +140,8 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
|
||||
return null;
|
||||
}
|
||||
// Skip methods declared on BeanFactoryAware and co.
|
||||
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
|
||||
// Skip setBeanFactory method on BeanFactoryAware.
|
||||
if (method.getDeclaringClass() == BeanFactoryAware.class) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+6
-24
@@ -18,8 +18,6 @@ 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;
|
||||
|
||||
@@ -32,45 +30,29 @@ import org.springframework.lang.Nullable;
|
||||
* for disabling caching, typically used for backing cache declarations
|
||||
* without an actual backing store.
|
||||
*
|
||||
* <p>Will simply accept any items into the cache not actually storing them.
|
||||
* <p>This implementation 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> caches = new ConcurrentHashMap<>(16);
|
||||
|
||||
private final Set<String> cacheNames = new LinkedHashSet<>(16);
|
||||
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(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
|
||||
@Nullable
|
||||
public Cache getCache(String name) {
|
||||
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);
|
||||
return this.cacheMap.computeIfAbsent(name, NoOpCache::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns the name of the caches previously requested.
|
||||
*/
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
synchronized (this.cacheNames) {
|
||||
return Collections.unmodifiableSet(this.cacheNames);
|
||||
}
|
||||
return Collections.unmodifiableSet(this.cacheMap.keySet());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-1
@@ -227,7 +227,6 @@ 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)
|
||||
|
||||
-3
@@ -78,8 +78,6 @@ 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");
|
||||
@@ -101,7 +99,6 @@ 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 {
|
||||
|
||||
|
||||
+22
-2
@@ -19,7 +19,6 @@ 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;
|
||||
|
||||
@@ -31,7 +30,7 @@ class ConcurrentMapCacheManagerTests {
|
||||
|
||||
@Test
|
||||
void testDynamicMode() {
|
||||
CacheManager cm = new ConcurrentMapCacheManager();
|
||||
ConcurrentMapCacheManager cm = new ConcurrentMapCacheManager();
|
||||
Cache cache1 = cm.getCache("c1");
|
||||
assertThat(cache1).isInstanceOf(ConcurrentMapCache.class);
|
||||
Cache cache1again = cm.getCache("c1");
|
||||
@@ -65,6 +64,14 @@ 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
|
||||
@@ -107,11 +114,24 @@ 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
|
||||
|
||||
+165
-2
@@ -20,8 +20,10 @@ 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;
|
||||
@@ -29,6 +31,8 @@ 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;
|
||||
@@ -104,6 +108,18 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
@Nullable
|
||||
private volatile Set<Map.Entry<K, V>> entrySet;
|
||||
|
||||
/**
|
||||
* Late binding key set.
|
||||
*/
|
||||
@Nullable
|
||||
private Set<K> keySet;
|
||||
|
||||
/**
|
||||
* Late binding values collection.
|
||||
*/
|
||||
@Nullable
|
||||
private Collection<V> values;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code ConcurrentReferenceHashMap} instance.
|
||||
@@ -528,6 +544,26 @@ 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;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <T> T doTask(@Nullable Object key, Task<T> task) {
|
||||
int hash = getHash(key);
|
||||
@@ -969,7 +1005,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
/**
|
||||
* Internal entry-set implementation.
|
||||
*/
|
||||
private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
|
||||
private final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<K, V>> iterator() {
|
||||
@@ -1005,13 +1041,140 @@ 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 class EntryIterator implements Iterator<Map.Entry<K, V>> {
|
||||
private final class EntryIterator implements Iterator<Map.Entry<K, V>> {
|
||||
|
||||
private int segmentIndex;
|
||||
|
||||
|
||||
@@ -145,26 +145,32 @@ final class UnmodifiableMultiValueMap<K,V> implements MultiValueMap<K,V>, Serial
|
||||
|
||||
@Override
|
||||
public Set<K> keySet() {
|
||||
if (this.keySet == null) {
|
||||
this.keySet = Collections.unmodifiableSet(this.delegate.keySet());
|
||||
Set<K> keySet = this.keySet;
|
||||
if (keySet == null) {
|
||||
keySet = Collections.unmodifiableSet(this.delegate.keySet());
|
||||
this.keySet = keySet;
|
||||
}
|
||||
return this.keySet;
|
||||
return keySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<K, List<V>>> entrySet() {
|
||||
if (this.entrySet == null) {
|
||||
this.entrySet = new UnmodifiableEntrySet<>(this.delegate.entrySet());
|
||||
Set<Entry<K, List<V>>> entrySet = this.entrySet;
|
||||
if (entrySet == null) {
|
||||
entrySet = new UnmodifiableEntrySet<>(this.delegate.entrySet());
|
||||
this.entrySet = entrySet;
|
||||
}
|
||||
return this.entrySet;
|
||||
return entrySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<List<V>> values() {
|
||||
if (this.values == null) {
|
||||
this.values = new UnmodifiableValueCollection<>(this.delegate.values());
|
||||
Collection<List<V>> values = this.values;
|
||||
if (values == null) {
|
||||
values = new UnmodifiableValueCollection<>(this.delegate.values());
|
||||
this.values = values;
|
||||
}
|
||||
return this.values;
|
||||
return values;
|
||||
}
|
||||
|
||||
// unsupported
|
||||
|
||||
+179
-13
@@ -16,8 +16,7 @@
|
||||
|
||||
package org.springframework.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
@@ -25,6 +24,8 @@ 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.junit.jupiter.api.Test;
|
||||
|
||||
@@ -32,12 +33,13 @@ import org.springframework.lang.Nullable;
|
||||
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}.
|
||||
@@ -47,8 +49,6 @@ import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
*/
|
||||
class ConcurrentReferenceHashMapTests {
|
||||
|
||||
private static final Comparator<? super String> NULL_SAFE_STRING_SORT = Comparators.nullsLow();
|
||||
|
||||
private TestWeakConcurrentCache<Integer, String> map = new TestWeakConcurrentCache<>();
|
||||
|
||||
|
||||
@@ -450,19 +450,174 @@ 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");
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -541,6 +696,17 @@ 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
|
||||
|
||||
+3
-4
@@ -40,11 +40,10 @@ import org.springframework.util.Assert;
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @see #loadBeanDefinitions
|
||||
* @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
|
||||
* @deprecated as of 5.3, in favor of Spring's common bean definition formats
|
||||
* and/or custom reader implementations
|
||||
* @deprecated in favor of Spring's common bean definition formats and/or
|
||||
* custom BeanDefinitionReader implementations
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(since = "5.3")
|
||||
public class JdbcBeanDefinitionReader {
|
||||
|
||||
private final org.springframework.beans.factory.support.PropertiesBeanDefinitionReader propReader;
|
||||
|
||||
+3
-3
@@ -25,7 +25,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.Aware;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.context.EmbeddedValueResolverAware;
|
||||
import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -167,8 +167,8 @@ public abstract class AbstractFallbackTransactionAttributeSource
|
||||
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
|
||||
return null;
|
||||
}
|
||||
// Skip methods declared on BeanFactoryAware and co.
|
||||
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
|
||||
// Skip setBeanFactory method on BeanFactoryAware.
|
||||
if (method.getDeclaringClass() == BeanFactoryAware.class) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+23
-5
@@ -32,6 +32,7 @@ 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;
|
||||
@@ -59,6 +60,7 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
|
||||
private final AnnotationTransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource();
|
||||
|
||||
|
||||
@Test
|
||||
void serializable() throws Exception {
|
||||
TestBean1 tb = new TestBean1();
|
||||
@@ -123,6 +125,10 @@ 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());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,6 +255,7 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
assertThat(actual.isReadOnly()).isTrue();
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class JtaAttributeTests {
|
||||
|
||||
@@ -276,6 +283,7 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
assertThat(getNameAttr.getPropagationBehavior()).isEqualTo(TransactionAttribute.PROPAGATION_SUPPORTS);
|
||||
}
|
||||
|
||||
|
||||
static class JtaAnnotatedBean1 implements ITestBean1 {
|
||||
|
||||
private String name;
|
||||
@@ -305,7 +313,6 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@jakarta.transaction.Transactional(jakarta.transaction.Transactional.TxType.SUPPORTS)
|
||||
static class JtaAnnotatedBean2 implements ITestBean1 {
|
||||
|
||||
@@ -362,7 +369,6 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@jakarta.transaction.Transactional(jakarta.transaction.Transactional.TxType.SUPPORTS)
|
||||
interface ITestJta {
|
||||
|
||||
@@ -375,9 +381,9 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
|
||||
void setName(String name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class Ejb3AttributeTests {
|
||||
|
||||
@@ -448,7 +454,6 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@jakarta.ejb.TransactionAttribute(TransactionAttributeType.SUPPORTS)
|
||||
static class Ejb3AnnotatedBean2 implements ITestBean1 {
|
||||
|
||||
@@ -506,6 +511,7 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class GroovyTests {
|
||||
|
||||
@@ -519,6 +525,7 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
assertThat(attributeSource.getTransactionAttribute(getMetaClassMethod, GroovyTestBean.class)).isNull();
|
||||
}
|
||||
|
||||
|
||||
@Transactional
|
||||
static class GroovyTestBean implements ITestBean1, GroovyObject {
|
||||
|
||||
@@ -571,6 +578,7 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Consumer<TransactionAttribute> hasRollbackRules(RollbackRuleAttribute... rollbackRuleAttributes) {
|
||||
return transactionAttribute -> {
|
||||
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
|
||||
@@ -626,7 +634,12 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
}
|
||||
|
||||
|
||||
interface ITestBean2X extends ITestBean2 {
|
||||
interface ITestBean2X extends ITestBean2, BeanNameAware {
|
||||
|
||||
@Transactional
|
||||
int getAge();
|
||||
|
||||
void setAge(int age);
|
||||
|
||||
String getName();
|
||||
|
||||
@@ -735,6 +748,10 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanName(String name) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
@@ -917,6 +934,7 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Transactional(label = {"retryable", "long-running"})
|
||||
static class TestBean11 {
|
||||
|
||||
|
||||
+9
-5
@@ -25,8 +25,8 @@ import org.springframework.lang.Nullable;
|
||||
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 ServletException for convenient throwing in any Servlet resource
|
||||
* (such as a Filter), and NestedServletException for proper root cause handling
|
||||
@@ -38,13 +38,14 @@ 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;
|
||||
|
||||
@Nullable
|
||||
private final Object[] messageDetailArguments;
|
||||
|
||||
@Nullable
|
||||
private ProblemDetail body;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor with a message only.
|
||||
@@ -108,7 +109,10 @@ public class ServletRequestBindingException extends ServletException implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProblemDetail getBody() {
|
||||
public synchronized ProblemDetail getBody() {
|
||||
if (this.body == null) {
|
||||
this.body = ProblemDetail.forStatus(getStatusCode());
|
||||
}
|
||||
return this.body;
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -124,7 +124,10 @@ class HtmlCharacterEntityDecoder {
|
||||
int value = (!isHexNumberedReference ?
|
||||
Integer.parseInt(getReferenceSubstring(2)) :
|
||||
Integer.parseInt(getReferenceSubstring(3), 16));
|
||||
this.decodedMessage.append((char) value);
|
||||
if (value > Character.MAX_CODE_POINT) {
|
||||
return false;
|
||||
}
|
||||
this.decodedMessage.appendCodePoint(value);
|
||||
return true;
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
|
||||
@@ -78,7 +78,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void httpMediaTypeNotSupportedException() {
|
||||
|
||||
List<MediaType> mediaTypes =
|
||||
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
|
||||
|
||||
@@ -96,7 +95,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void httpMediaTypeNotSupportedExceptionWithParseError() {
|
||||
|
||||
ErrorResponse ex = new HttpMediaTypeNotSupportedException(
|
||||
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
|
||||
|
||||
@@ -109,7 +107,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void httpMediaTypeNotAcceptableException() {
|
||||
|
||||
List<MediaType> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
|
||||
HttpMediaTypeNotAcceptableException ex = new HttpMediaTypeNotAcceptableException(mediaTypes);
|
||||
|
||||
@@ -123,7 +120,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void httpMediaTypeNotAcceptableExceptionWithParseError() {
|
||||
|
||||
ErrorResponse ex = new HttpMediaTypeNotAcceptableException(
|
||||
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
|
||||
|
||||
@@ -136,7 +132,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void asyncRequestTimeoutException() {
|
||||
|
||||
ErrorResponse ex = new AsyncRequestTimeoutException();
|
||||
assertDetailMessageCode(ex, null, null);
|
||||
|
||||
@@ -148,7 +143,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void httpRequestMethodNotSupportedException() {
|
||||
|
||||
HttpRequestMethodNotSupportedException ex =
|
||||
new HttpRequestMethodNotSupportedException("PUT", Arrays.asList("GET", "POST"));
|
||||
|
||||
@@ -162,7 +156,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void missingRequestHeaderException() {
|
||||
|
||||
MissingRequestHeaderException ex = new MissingRequestHeaderException("Authorization", this.methodParameter);
|
||||
|
||||
assertStatus(ex, HttpStatus.BAD_REQUEST);
|
||||
@@ -174,7 +167,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void missingServletRequestParameterException() {
|
||||
|
||||
MissingServletRequestParameterException ex = new MissingServletRequestParameterException("query", "String");
|
||||
|
||||
assertStatus(ex, HttpStatus.BAD_REQUEST);
|
||||
@@ -186,10 +178,8 @@ 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()});
|
||||
@@ -199,7 +189,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void missingPathVariableException() {
|
||||
|
||||
MissingPathVariableException ex = new MissingPathVariableException("id", this.methodParameter);
|
||||
|
||||
assertStatus(ex, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
@@ -210,8 +199,18 @@ class ErrorResponseExceptionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingRequestCookieException() {
|
||||
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);
|
||||
@@ -223,7 +222,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void unsatisfiedServletRequestParameterException() {
|
||||
|
||||
UnsatisfiedServletRequestParameterException ex = new UnsatisfiedServletRequestParameterException(
|
||||
new String[] { "foo=bar", "bar=baz" }, Collections.singletonMap("q", new String[] {"1"}));
|
||||
|
||||
@@ -236,7 +234,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void missingServletRequestPartException() {
|
||||
|
||||
MissingServletRequestPartException ex = new MissingServletRequestPartException("file");
|
||||
|
||||
assertStatus(ex, HttpStatus.BAD_REQUEST);
|
||||
@@ -248,7 +245,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void methodArgumentNotValidException() {
|
||||
|
||||
ValidationTestHelper testHelper = new ValidationTestHelper(MethodArgumentNotValidException.class);
|
||||
BindingResult result = testHelper.bindingResult();
|
||||
|
||||
@@ -280,7 +276,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void unsupportedMediaTypeStatusException() {
|
||||
|
||||
List<MediaType> mediaTypes =
|
||||
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
|
||||
|
||||
@@ -298,7 +293,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void unsupportedMediaTypeStatusExceptionWithParseError() {
|
||||
|
||||
ErrorResponse ex = new UnsupportedMediaTypeStatusException(
|
||||
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
|
||||
|
||||
@@ -311,7 +305,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void notAcceptableStatusException() {
|
||||
|
||||
List<MediaType> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
|
||||
NotAcceptableStatusException ex = new NotAcceptableStatusException(mediaTypes);
|
||||
|
||||
@@ -325,7 +318,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void notAcceptableStatusExceptionWithParseError() {
|
||||
|
||||
ErrorResponse ex = new NotAcceptableStatusException(
|
||||
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
|
||||
|
||||
@@ -338,7 +330,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void serverErrorException() {
|
||||
|
||||
ServerErrorException ex = new ServerErrorException("Failure", null);
|
||||
|
||||
assertStatus(ex, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
@@ -350,7 +341,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void missingRequestValueException() {
|
||||
|
||||
MissingRequestValueException ex =
|
||||
new MissingRequestValueException("foo", String.class, "header", this.methodParameter);
|
||||
|
||||
@@ -363,7 +353,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void unsatisfiedRequestParameterException() {
|
||||
|
||||
UnsatisfiedRequestParameterException ex =
|
||||
new UnsatisfiedRequestParameterException(
|
||||
Arrays.asList("foo=bar", "bar=baz"),
|
||||
@@ -378,7 +367,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void webExchangeBindException() {
|
||||
|
||||
ValidationTestHelper testHelper = new ValidationTestHelper(WebExchangeBindException.class);
|
||||
BindingResult result = testHelper.bindingResult();
|
||||
|
||||
@@ -393,7 +381,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void methodNotAllowedException() {
|
||||
|
||||
List<HttpMethod> supportedMethods = Arrays.asList(HttpMethod.GET, HttpMethod.POST);
|
||||
MethodNotAllowedException ex = new MethodNotAllowedException(HttpMethod.PUT, supportedMethods);
|
||||
|
||||
@@ -407,7 +394,6 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void methodNotAllowedExceptionWithoutSupportedMethods() {
|
||||
|
||||
MethodNotAllowedException ex = new MethodNotAllowedException(HttpMethod.PUT, Collections.emptyList());
|
||||
|
||||
assertStatus(ex, HttpStatus.METHOD_NOT_ALLOWED);
|
||||
@@ -417,9 +403,8 @@ class ErrorResponseExceptionTests {
|
||||
assertThat(ex.getHeaders()).isEmpty();
|
||||
}
|
||||
|
||||
@Test // gh-30300
|
||||
@Test // gh-30300
|
||||
void responseStatusException() {
|
||||
|
||||
Locale locale = Locale.UK;
|
||||
LocaleContextHolder.setLocale(locale);
|
||||
|
||||
@@ -519,7 +504,6 @@ 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,6 +21,8 @@ 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
|
||||
@@ -28,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
class HtmlUtilsTests {
|
||||
|
||||
@Test
|
||||
void testHtmlEscape() {
|
||||
void htmlEscape() {
|
||||
String unescaped = "\"This is a quote'";
|
||||
String escaped = HtmlUtils.htmlEscape(unescaped);
|
||||
assertThat(escaped).isEqualTo(""This is a quote'");
|
||||
@@ -39,14 +41,7 @@ class HtmlUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHtmlUnescape() {
|
||||
String escaped = ""This is a quote'";
|
||||
String unescaped = HtmlUtils.htmlUnescape(escaped);
|
||||
assertThat(unescaped).isEqualTo("\"This is a quote'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEncodeIntoHtmlCharacterSet() {
|
||||
void htmlEscapeIntoHtmlCharacterSet() {
|
||||
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.");
|
||||
|
||||
@@ -60,12 +55,11 @@ class HtmlUtilsTests {
|
||||
assertThat(HtmlUtils.htmlEscapeDecimal("" + (char) 977)).as("The special character 977 should be encoded to 'ϑ'").isEqualTo("ϑ");
|
||||
}
|
||||
|
||||
// SPR-9293
|
||||
@Test
|
||||
void testEncodeIntoHtmlCharacterSetFromUtf8() {
|
||||
@Test // SPR-9293
|
||||
void htmlEscapeIntoHtmlCharacterSetFromUtf8() {
|
||||
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 '< >'").isEqualTo("< >");
|
||||
@@ -75,7 +69,38 @@ class HtmlUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDecodeFromHtmlCharacterSet() {
|
||||
void htmlUnescape() {
|
||||
String escaped = ""This is a quote'";
|
||||
String unescaped = HtmlUtils.htmlUnescape(escaped);
|
||||
assertThat(unescaped).isEqualTo("\"This is a quote'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void htmlUnescapeHandlesSupplementaryCharactersAsDecimal() {
|
||||
String expectedCharacter = "😀";
|
||||
String decimalEntity = "😀";
|
||||
String actualResultFromDecimal = HtmlUtils.htmlUnescape(decimalEntity);
|
||||
assertThat(actualResultFromDecimal).as("Decimal entity was not converted correctly.").isEqualTo(expectedCharacter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void htmlUnescapeHandlesSupplementaryCharactersAsHexadecimal() {
|
||||
String expectedCharacter = "😀";
|
||||
String hexEntity = "😀";
|
||||
String actualResultFromHex = HtmlUtils.htmlUnescape(hexEntity);
|
||||
assertThat(actualResultFromHex).as("Hexadecimal entity was not converted correctly.").isEqualTo(expectedCharacter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void htmlUnescapeHandlesBasicEntities() {
|
||||
String input = "<p>Tom & Jerry's "Show"</p>";
|
||||
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() {
|
||||
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.");
|
||||
|
||||
|
||||
@@ -128,6 +128,7 @@
|
||||
<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"/>
|
||||
|
||||
Reference in New Issue
Block a user