Compare commits

..

1 Commits

Author SHA1 Message Date
Stéphane Nicoll 6b117247d3 Release v6.2.18 2026-04-17 08:26:24 +02:00
172 changed files with 699 additions and 2006 deletions
-4
View File
@@ -17,10 +17,6 @@ antora {
]
}
node {
version = '24.15.0'
}
tasks.named("generateAntoraYml") {
asciidocAttributes = project.provider( {
return ["spring-version": project.version ]
@@ -74,11 +74,6 @@ expressions used in XML bean definitions, `@Value`, etc.
| The mode to use when compiling expressions for the
xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language].
| `spring.expression.maxOperations`
| The default maximum number of operations permitted during
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
expression evaluation.
| `spring.getenv.ignore`
| Instructs Spring to ignore operating system environment variables if a Spring
`Environment` property -- for example, a placeholder in a configuration String -- isn't
@@ -395,16 +395,6 @@ set a JVM system property or Spring property named `spring.context.expression.ma
to the maximum expression length needed by your application (see
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
Similarly, the number of operations performed during the evaluation of a SpEL expression
cannot exceed 10,000 by default; however, the `maxOperations` value is configurable. If
you create a `SpelExpressionParser` programmatically (the recommend approach), you can
specify a custom `maxOperations` value when creating the `SpelParserConfiguration` that
you provide to the `SpelExpressionParser`. If you are not able to configure an explicit
value for `maxOperations` via `SpelParserConfiguration`, you can set a JVM system
property or Spring property named `spring.expression.maxOperations` to the maximum number
of operations required by your application (see
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
[[expressions-spel-compilation]]
== SpEL Compilation
@@ -22,7 +22,7 @@ where all the underlying resources have to participate in the service-level tran
NOTE: By default, a participating transaction joins the characteristics of the outer scope,
silently ignoring the local isolation level, timeout value, or read-only flag (if any).
Consider switching the `validateExistingTransaction` flag to `true` on your transaction
Consider switching the `validateExistingTransactions` flag to `true` on your transaction
manager if you want isolation level declarations to be rejected when participating in
an existing transaction with a different isolation level. This non-lenient mode also
rejects read-only mismatches (that is, an inner read-write transaction that tries to participate
@@ -3,7 +3,7 @@
In the previous sections, we have seen how to use MockMvc in conjunction with the raw
HtmlUnit APIs. In this section, we use additional abstractions within the Selenium
https://www.selenium.dev/documentation/webdriver/[WebDriver] to make things even easier.
https://docs.seleniumhq.org/projects/webdriver/[WebDriver] to make things even easier.
[[mockmvc-server-htmlunit-webdriver-why]]
== Why WebDriver and MockMvc?
@@ -12,8 +12,8 @@ We can already use HtmlUnit and MockMvc, so why would we want to use WebDriver?
Selenium WebDriver provides a very elegant API that lets us easily organize our code. To
better show how it works, we explore an example in this section.
NOTE: Despite being a part of https://www.selenium.dev/documentation/[Selenium],
WebDriver does not require a Selenium Server to run your tests.
NOTE: Despite being a part of https://docs.seleniumhq.org/[Selenium], WebDriver does not
require a Selenium Server to run your tests.
Suppose we need to ensure that a message is created properly. The tests involve finding
the HTML form input elements, filling them out, and making various assertions.
@@ -308,7 +308,7 @@ interested. These are of type `WebElement`. WebDriver's
https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a
lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving
each `WebElement`. The
https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
method automatically resolves each `WebElement` by using the field name and looking it up
by the `id` or `name` of the element within the HTML page.
<3> We can use the
@@ -352,7 +352,7 @@ interested. These are of type `WebElement`. WebDriver's
https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a
lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving
each `WebElement`. The
https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
method automatically resolves each `WebElement` by using the field name and looking it up
by the `id` or `name` of the element within the HTML page.
<3> We can use the
+5 -5
View File
@@ -1,11 +1,11 @@
{
"dependencies": {
"antora": "3.2.0-alpha.12",
"antora": "3.2.0-alpha.11",
"@antora/atlas-extension": "1.0.0-alpha.5",
"@antora/collector-extension": "1.0.3",
"@antora/collector-extension": "1.0.2",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.14.12",
"fast-xml-parser": "5.7.0",
"@springio/asciidoctor-extensions": "1.0.0-alpha.18"
"@springio/antora-extensions": "1.14.7",
"fast-xml-parser": "5.3.8",
"@springio/asciidoctor-extensions": "1.0.0-alpha.17"
}
}
+4 -4
View File
@@ -8,10 +8,10 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.18.5"))
api(platform("io.micrometer:micrometer-bom:1.15.12"))
api(platform("io.netty:netty-bom:4.1.134.Final"))
api(platform("io.micrometer:micrometer-bom:1.15.11"))
api(platform("io.netty:netty-bom:4.1.132.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2024.0.18"))
api(platform("io.projectreactor:reactor-bom:2024.0.17"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:4.0.31"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
@@ -20,7 +20,7 @@ dependencies {
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.33"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
api(platform("org.junit:junit-bom:5.14.4"))
api(platform("org.junit:junit-bom:5.14.3"))
api(platform("org.mockito:mockito-bom:5.23.0"))
constraints {
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.2.19
version=6.2.18
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
@@ -171,7 +171,7 @@ public class MethodInvocationProceedingJoinPoint implements ProceedingJoinPoint,
@Override
public String toString() {
return "execution(" + getSignature() + ")";
return "execution(" + getSignature().toString() + ")";
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -967,8 +967,8 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
actualName = propertyName.substring(0, keyStart);
}
String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd);
if (key.length() > 1 && ((key.startsWith("'") && key.endsWith("'")) ||
(key.startsWith("\"") && key.endsWith("\"")))) {
if (key.length() > 1 && (key.startsWith("'") && key.endsWith("'")) ||
(key.startsWith("\"") && key.endsWith("\""))) {
key = key.substring(1, key.length() - 1);
}
keys.add(key);
@@ -63,14 +63,12 @@ public interface ConfigurablePropertyAccessor extends PropertyAccessor, Property
* <p>If {@code true}, a {@code null} path location will be populated
* with a default object value and traversed instead of resulting in a
* {@link NullValueInNestedPathException}.
* <p>Default is {@code false} on a plain accessor.
* @since 4.1
* <p>Default is {@code false} on a plain PropertyAccessor instance.
*/
void setAutoGrowNestedPaths(boolean autoGrowNestedPaths);
/**
* Return whether "auto-growing" of nested paths has been activated.
* @since 4.1
*/
boolean isAutoGrowNestedPaths();
@@ -154,8 +154,7 @@ public abstract class PropertyAccessorUtils {
PropertyAccessor.PROPERTY_KEY_SUFFIX, keyStart + PropertyAccessor.PROPERTY_KEY_PREFIX.length());
if (keyEnd != -1) {
String key = sb.substring(keyStart + PropertyAccessor.PROPERTY_KEY_PREFIX.length(), keyEnd);
if (key.length() > 1 && ((key.startsWith("'") && key.endsWith("'")) ||
(key.startsWith("\"") && key.endsWith("\"")))) {
if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) {
sb.delete(keyStart + 1, keyStart + 2);
sb.delete(keyEnd - 2, keyEnd - 1);
keyEnd = keyEnd - 2;
@@ -31,7 +31,6 @@ public class AotBeanProcessingException extends AotProcessingException {
private final RootBeanDefinition beanDefinition;
/**
* Create an instance with the {@link RegisteredBean} that fails to be
* processed, a detail message, and an optional root cause.
@@ -65,7 +64,6 @@ public class AotBeanProcessingException extends AotProcessingException {
return sb.toString();
}
/**
* Return the bean definition of the bean that failed to be processed.
*/
@@ -249,7 +249,7 @@ public abstract class YamlProcessor {
}
else {
// It has to be a map key in this case
result.put("[" + key + "]", value);
result.put("[" + key.toString() + "]", value);
}
});
return result;
@@ -610,7 +610,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) {
throw bce;
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
else {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
}
}
if (earlySingletonExposure) {
@@ -1255,8 +1257,8 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
instance = obtainInstanceFromSupplier(supplier, beanName, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) {
throw bce;
if (ex instanceof BeansException beansException) {
throw beansException;
}
throw new BeanCreationException(beanName, "Instantiation of supplied bean failed", ex);
}
@@ -1164,19 +1164,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
if (mbd.isBackgroundInit()) {
Executor executor = getBootstrapExecutor();
if (executor != null) {
// Force initialization of depends-on beans in mainline thread.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
getBean(dep);
}
}
// Force initialization of factory reference in mainline thread.
String factoryBeanName = mbd.getFactoryBeanName();
if (factoryBeanName != null) {
getBean(factoryBeanName);
}
// Instantiate current bean in background thread.
CompletableFuture<?> future = CompletableFuture.runAsync(
() -> instantiateSingletonInBackgroundThread(beanName), executor);
addSingletonFactory(beanName, () -> {
@@ -1413,8 +1413,6 @@ abstract class AbstractPropertyAccessorTests {
assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name8");
assertThat(accessor.getPropertyValue("map['key5[foo]'].name")).isEqualTo("name8");
assertThat(accessor.getPropertyValue("map[\"key5[foo]\"].name")).isEqualTo("name8");
assertThat(accessor.getPropertyValue("map['].name")).isEqualTo("name9");
assertThat(accessor.getPropertyValue("map[\"].name")).isEqualTo("name9");
assertThat(accessor.getPropertyValue("iterableMap[key1].name")).isEqualTo("nameC");
assertThat(accessor.getPropertyValue("iterableMap[key2][0].name")).isEqualTo("nameA");
assertThat(accessor.getPropertyValue("iterableMap[key2][1].name")).isEqualTo("nameB");
@@ -79,10 +79,6 @@ class PropertyAccessorUtilsTests {
assertThat(PropertyAccessorUtils.canonicalPropertyName("map[key1].name")).isEqualTo("map[key1].name");
assertThat(PropertyAccessorUtils.canonicalPropertyName("map['key1'].name")).isEqualTo("map[key1].name");
assertThat(PropertyAccessorUtils.canonicalPropertyName("map[\"key1\"].name")).isEqualTo("map[key1].name");
assertThat(PropertyAccessorUtils.canonicalPropertyName("map['key1]")).isEqualTo("map['key1]");
assertThat(PropertyAccessorUtils.canonicalPropertyName("map[\"key1]")).isEqualTo("map[\"key1]");
assertThat(PropertyAccessorUtils.canonicalPropertyName("map[']")).isEqualTo("map[']");
assertThat(PropertyAccessorUtils.canonicalPropertyName("map[\"]")).isEqualTo("map[\"]");
}
@Test
@@ -76,7 +76,6 @@ public class IndexedTestBean {
TestBean tb6 = new TestBean("name6", 0);
TestBean tb7 = new TestBean("name7", 0);
TestBean tb8 = new TestBean("name8", 0);
TestBean tb9 = new TestBean("name9", 0);
TestBean tbA = new TestBean("nameA", 0);
TestBean tbB = new TestBean("nameB", 0);
TestBean tbC = new TestBean("nameC", 0);
@@ -105,8 +104,6 @@ public class IndexedTestBean {
list.add(tbY);
this.map.put("key4", list);
this.map.put("key5[foo]", tb8);
this.map.put("'", tb9);
this.map.put("\"", tb9);
this.myTestBeans = new MyTestBeans(tbZ);
}
@@ -63,7 +63,7 @@ class TypeHelper {
if (type instanceof DeclaredType declaredType) {
Element enclosingElement = declaredType.asElement().getEnclosingElement();
if (enclosingElement instanceof TypeElement) {
return getQualifiedName(enclosingElement) + "$" + declaredType.asElement().getSimpleName();
return getQualifiedName(enclosingElement) + "$" + declaredType.asElement().getSimpleName().toString();
}
else {
return getQualifiedName(declaredType.asElement());
@@ -201,13 +201,13 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
private void validateCacheOperation(AnnotatedElement ae, CacheOperation operation) {
if (StringUtils.hasText(operation.getKey()) && StringUtils.hasText(operation.getKeyGenerator())) {
throw new IllegalStateException("Invalid cache annotation configuration on '" +
ae + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
ae.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
"These attributes are mutually exclusive: either set the SpEL expression used to" +
"compute the key at runtime or set the name of the KeyGenerator bean to use.");
}
if (StringUtils.hasText(operation.getCacheManager()) && StringUtils.hasText(operation.getCacheResolver())) {
throw new IllegalStateException("Invalid cache annotation configuration on '" +
ae + "'. Both 'cacheManager' and 'cacheResolver' attributes have been set. " +
ae.toString() + "'. Both 'cacheManager' and 'cacheResolver' attributes have been set. " +
"These attributes are mutually exclusive: the cache manager is used to configure a" +
"default cache resolver if none is set. If a cache resolver is set, the cache manager" +
"won't be used.");
@@ -223,7 +223,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
if (StringUtils.hasText(builder.getKey()) && StringUtils.hasText(builder.getKeyGenerator())) {
throw new IllegalStateException("Invalid cache advice configuration on '" +
element + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
element.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
"These attributes are mutually exclusive: either set the SpEL expression used to" +
"compute the key at runtime or set the name of the KeyGenerator bean to use.");
}
@@ -87,7 +87,7 @@ public abstract class AbstractValueAdaptingCache implements Cache {
*/
@Nullable
protected Object fromStoreValue(@Nullable Object storeValue) {
if (this.allowNullValues && storeValue instanceof NullValue) {
if (this.allowNullValues && storeValue == NullValue.INSTANCE) {
return null;
}
return storeValue;
@@ -885,13 +885,16 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
});
GeneratedMethod generateMethod = generatedClass.getMethods().add("apply", method -> {
method.addJavadoc("Apply resource autowiring.");
method.addModifiers(javax.lang.model.element.Modifier.PUBLIC, javax.lang.model.element.Modifier.STATIC);
method.addModifiers(javax.lang.model.element.Modifier.PUBLIC,
javax.lang.model.element.Modifier.STATIC);
method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER);
method.addParameter(this.target, INSTANCE_PARAMETER);
method.returns(this.target);
method.addCode(generateMethodCode(generatedClass.getName(), generationContext.getRuntimeHints()));
method.addCode(generateMethodCode(generatedClass.getName(),
generationContext.getRuntimeHints()));
});
beanRegistrationCode.addInstancePostProcessor(generateMethod.toMethodReference());
registerHints(generationContext.getRuntimeHints());
}
@@ -258,11 +258,9 @@ class ConfigurationClassParser {
return;
}
else if (configClass.isScanned()) {
if (existingClass.isImported()) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
// An implicitly scanned bean definition should not override an explicit import.
return;
@@ -195,7 +195,7 @@ public class InstrumentationLoadTimeWeaver implements LoadTimeWeaver {
@Override
public String toString() {
return "FilteringClassFileTransformer for: " + this.targetTransformer;
return "FilteringClassFileTransformer for: " + this.targetTransformer.toString();
}
}
@@ -177,11 +177,7 @@ final class BitsCronField extends CronField {
int next = nextSetBit(current);
if (next == -1) {
temporal = type().rollForward(temporal);
next = nextSetBit(type().get(temporal));
if (next == -1) {
temporal = type().rollForward(temporal);
next = nextSetBit(0);
}
next = nextSetBit(0);
}
if (next == current) {
return temporal;
@@ -195,12 +191,7 @@ final class BitsCronField extends CronField {
next = nextSetBit(current);
if (next == -1) {
temporal = type().rollForward(temporal);
next = nextSetBit(type().get(temporal));
if (next == -1) {
temporal = type().rollForward(temporal);
next = nextSetBit(0);
}
current = type().get(temporal);
next = nextSetBit(0);
}
}
if (count >= CronExpression.MAX_ATTEMPTS) {
@@ -286,9 +286,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
* Specify the limit for array and collection auto-growing.
* <p>Default is 256, preventing OutOfMemoryErrors in case of large indexes.
* Raise this limit if your auto-growing needs are unusually high.
* <p>Used for setter injection via {@link #bind(PropertyValues)};
* not applicable to field injection, and not to constructor binding
* via {@link #construct} either.
* <p>Used for setter/field injection via {@link #bind(PropertyValues)}, and not
* applicable to constructor binding via {@link #construct}.
* @see #initBeanPropertyAccess()
* @see org.springframework.beans.BeanWrapper#setAutoGrowCollectionLimit
*/
@@ -197,16 +197,6 @@ class BackgroundBootstrapTests {
}
}
@Test
@Timeout(10)
@EnabledForTestGroups(LONG_RUNNING)
void bootstrapWithCustomExecutorAndLazyConfig() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CustomExecutorLazyBeanConfig.class);
assertThat(ctx.getBeanFactory().containsSingleton("testBean1")).isTrue();
assertThat(ctx.getBeanFactory().containsSingleton("testBean2")).isTrue();
ctx.close();
}
@Configuration(proxyBeanMethods = false)
static class UnmanagedThreadBeanConfig {
@@ -552,36 +542,4 @@ class BackgroundBootstrapTests {
}
}
@Configuration(proxyBeanMethods = false)
static class CustomExecutorLazyBeanConfig {
@Bean
public ThreadPoolTaskExecutor bootstrapExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("Custom-");
executor.setCorePoolSize(2);
executor.initialize();
return executor;
}
@Configuration(proxyBeanMethods = false)
@Lazy
static class LazyBeanConfig {
@Bean(bootstrap = BACKGROUND)
public TestBean testBean1() throws InterruptedException {
Thread.sleep(6000);
return new TestBean();
}
@Bean(bootstrap = BACKGROUND)
@Lazy
public TestBean testBean2() throws InterruptedException {
Thread.sleep(6000);
return new TestBean();
}
}
}
}
@@ -26,7 +26,6 @@ import example.scannable.CustomComponent;
import example.scannable.CustomStereotype;
import example.scannable.DefaultNamedComponent;
import example.scannable.FooService;
import example.scannable.FooServiceImpl;
import example.scannable.MessageBean;
import example.scannable.ScopedProxyTestBean;
import example.scannable_implicitbasepackage.ComponentScanAnnotatedConfigWithImplicitBasePackage;
@@ -44,7 +43,6 @@ import org.springframework.beans.factory.annotation.CustomAutowireConfigurer;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
@@ -86,17 +84,6 @@ class ComponentScanAnnotationIntegrationTests {
assertContextContainsBean(ctx, "fooServiceImpl");
}
@Test
void controlScanWithExplicitRegistration() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.registerBeanDefinition("myFooService", new RootBeanDefinition(FooServiceImpl.class));
ctx.scan(example.scannable.PackageMarker.class.getPackage().getName());
ctx.refresh();
assertContextContainsBean(ctx, "myFooService");
assertContextContainsBean(ctx, "fooServiceImpl");
}
@Test
void viaContextRegistration() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanAnnotatedConfig.class);
@@ -289,7 +289,7 @@ class AutowiredConfigurationTests {
@Bean
public TestBean testBean(Color color, List<Color> colors) {
return new TestBean(color + "-" + colors.get(0));
return new TestBean(color.toString() + "-" + colors.get(0).toString());
}
}
@@ -303,7 +303,7 @@ class AutowiredConfigurationTests {
return new TestBean("");
}
else {
return new TestBean(color.get() + "-" + colors.get().get(0));
return new TestBean(color.get() + "-" + colors.get().get(0).toString());
}
}
}
@@ -16,7 +16,6 @@
package org.springframework.scheduling.support;
import java.time.ZonedDateTime;
import java.util.Arrays;
import org.assertj.core.api.Condition;
@@ -30,7 +29,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*
* @author Arjen Poutsma
* @author Sam Brannen
* @author Brian Clozel
*/
class BitsCronFieldTests {
@@ -114,24 +112,6 @@ class BitsCronFieldTests {
.has(clear(0)).has(setRange(1, 7));
}
@Test
void nextOrSameWithMidnightGap() {
BitsCronField field = BitsCronField.parseHours("0-23/2");
ZonedDateTime last = ZonedDateTime.parse("2025-04-24T23:00:00+02:00[Africa/Cairo]");
ZonedDateTime expected = ZonedDateTime.parse("2025-04-25T02:00:00+03:00[Africa/Cairo]");
ZonedDateTime actual = field.nextOrSame(last);
assertThat(actual).isEqualTo(expected);
}
@Test
void nextOrSameWithGapAfterRollForward() {
BitsCronField field = BitsCronField.parseHours("0,2");
ZonedDateTime last = ZonedDateTime.parse("2026-03-08T01:00:00-05:00[America/New_York]");
ZonedDateTime expected = ZonedDateTime.parse("2026-03-09T00:00:00-04:00[America/New_York]");
ZonedDateTime actual = field.nextOrSame(last);
assertThat(actual).isEqualTo(expected);
}
private static Condition<BitsCronField> set(int... indices) {
return new Condition<>(String.format("set bits %s", Arrays.toString(indices))) {
@@ -1367,14 +1367,6 @@ class CronExpressionTests {
actual = cronExpression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
cronExpression = CronExpression.parse("0 0 */2 * * ?");
last = ZonedDateTime.parse("2025-04-24T22:00:00+02:00[Africa/Cairo]");
expected = ZonedDateTime.parse("2025-04-25T02:00:00+03:00[Africa/Cairo]");
actual = cronExpression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
}
@Test
@@ -24,9 +24,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.Cache;
import org.springframework.cache.support.NullValue;
import static org.assertj.core.api.Assertions.assertThat;
@@ -74,12 +72,6 @@ public abstract class AbstractCacheTests<T extends Cache> {
assertThat(cache.get(key).get()).isNull();
assertThat(cache.get(key, String.class)).isNull();
assertThat(cache.get(key, Object.class)).isNull();
cache.put(key, BeanUtils.instantiateClass(NullValue.class));
assertThat(cache.get(key)).isNotNull();
assertThat(cache.get(key).get()).isNull();
assertThat(cache.get(key, String.class)).isNull();
assertThat(cache.get(key, Object.class)).isNull();
}
@Test
@@ -36,9 +36,10 @@ public abstract class AbstractValueAdaptingCacheTests<T extends AbstractValueAda
protected void cachePutNullValueAllowNullFalse() {
T cache = getCache(false);
String key = createRandomKey();
assertThatIllegalArgumentException().isThrownBy(() -> cache.put(key, null))
.withMessageContaining(CACHE_NAME_NO_NULL)
.withMessageContaining("is configured to not allow null values but null was provided");
assertThatIllegalArgumentException().isThrownBy(() ->
cache.put(key, null))
.withMessageContaining(CACHE_NAME_NO_NULL)
.withMessageContaining("is configured to not allow null values but null was provided");
}
}
@@ -145,7 +145,7 @@ implements CallbackGenerator
private static void superHelper(CodeEmitter e, MethodInfo method, Context context)
{
if (TypeUtils.isAbstract(method.getModifiers())) {
e.throw_exception(ABSTRACT_METHOD_ERROR, method + " is abstract" );
e.throw_exception(ABSTRACT_METHOD_ERROR, method.toString() + " is abstract" );
} else {
e.load_this();
context.emitLoadArgsAndInvoke(e, method);
@@ -129,10 +129,9 @@ public abstract class CoroutinesUtils {
Object arg = args[index];
if (!(parameter.isOptional() && arg == null)) {
KType type = parameter.getType();
if (!(type.isMarkedNullable() && arg == null) &&
if (!type.isMarkedNullable() &&
type.getClassifier() instanceof KClass<?> kClass &&
KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass)) &&
!JvmClassMappingKt.getJavaClass(kClass).isInstance(arg)) {
KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
arg = box(kClass, arg);
}
argMap.put(parameter, arg);
@@ -162,10 +161,9 @@ public abstract class CoroutinesUtils {
private static Object box(KClass<?> kClass, @Nullable Object arg) {
KFunction<?> constructor = Objects.requireNonNull(KClasses.getPrimaryConstructor(kClass));
KType type = constructor.getParameters().get(0).getType();
if (!(type.isMarkedNullable() && arg == null) &&
if (!type.isMarkedNullable() &&
type.getClassifier() instanceof KClass<?> parameterClass &&
KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(parameterClass)) &&
!JvmClassMappingKt.getJavaClass(parameterClass).isInstance(arg)) {
KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(parameterClass))) {
arg = box(parameterClass, arg);
}
if (!KCallablesJvm.isAccessible(constructor)) {
@@ -121,8 +121,8 @@ public class MethodParameter {
* return type; 0 for the first method parameter; 1 for the second method
* parameter, etc.
* @param nestingLevel the nesting level of the target type
* (1 for the top-level type; in case of a List of Lists, 2 would indicate
* the nested List, whereas 3 would indicate the element of the nested List)
* (typically 1; for example, in case of a List of Lists, 1 would indicate the
* nested List, whereas 2 would indicate the element of the nested List)
*/
public MethodParameter(Method method, int parameterIndex, int nestingLevel) {
Assert.notNull(method, "Method must not be null");
@@ -145,8 +145,8 @@ public class MethodParameter {
* @param constructor the Constructor to specify a parameter for
* @param parameterIndex the index of the parameter
* @param nestingLevel the nesting level of the target type
* (1 for the top-level type; in case of a List of Lists, 2 would indicate
* the nested List, whereas 3 would indicate the element of the nested List)
* (typically 1; for example, in case of a List of Lists, 1 would indicate the
* nested List, whereas 2 would indicate the element of the nested List)
*/
public MethodParameter(Constructor<?> constructor, int parameterIndex, int nestingLevel) {
Assert.notNull(constructor, "Constructor must not be null");
@@ -296,8 +296,8 @@ public class MethodParameter {
/**
* Return the nesting level of the target type
* (1 for the top-level type; in case of a List of Lists, 2 would indicate
* the nested List, whereas 3 would indicate the element of the nested List).
* (typically 1; for example, in case of a List of Lists, 1 would indicate the
* nested List, whereas 2 would indicate the element of the nested List).
*/
public int getNestingLevel() {
return this.nestingLevel;
@@ -1548,6 +1548,9 @@ public class ResolvableType implements Serializable {
return new ResolvableType(type, null, typeProvider, variableResolver);
}
// Purge empty entries on access since we don't have a clean-up thread or the like.
cache.purgeUnreferencedEntries();
// Check the cache - we may have a ResolvableType which has been resolved before...
ResolvableType resultType = new ResolvableType(type, typeProvider, variableResolver);
ResolvableType cachedType = cache.get(resultType);
@@ -44,7 +44,6 @@ import org.springframework.lang.Nullable;
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#STRICT_LOCKING_PROPERTY_NAME
* @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME
* @see org.springframework.expression.spel.SpelParserConfiguration#SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME
* @see org.springframework.expression.spel.SpelParserConfiguration#SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME
* @see org.springframework.jdbc.core.StatementCreatorUtils#IGNORE_GETPARAMETERTYPE_PROPERTY_NAME
* @see org.springframework.jndi.JndiLocatorDelegate#IGNORE_JNDI_PROPERTY_NAME
* @see org.springframework.objenesis.SpringObjenesis#IGNORE_OBJENESIS_PROPERTY_NAME
@@ -38,28 +38,14 @@ public final class SpringVersion {
/**
* Return the "major.minor.patch" version string of the present Spring codebase,
* Return the full version string of the present Spring codebase,
* or {@code null} if it cannot be determined.
* @see Package#getImplementationVersion()
*/
@Nullable
public static String getVersion() {
Package pkg = SpringVersion.class.getPackage();
String version = (pkg != null ? pkg.getImplementationVersion() : null);
if (version != null) {
int idx = version.indexOf('.'); // after major
if (idx != -1) {
idx = version.indexOf('.', idx + 1); // after minor
if (idx != -1) {
idx = version.indexOf('.', idx + 1); // after patch
if (idx != -1) {
// Ignore anything beyond "major.minor.patch"
version = version.substring(0, idx);
}
}
}
}
return version;
return (pkg != null ? pkg.getImplementationVersion() : null);
}
}
@@ -1138,11 +1138,9 @@ public abstract class DataBufferUtils {
protected void hookOnNext(DataBuffer dataBuffer) {
try {
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) {
while (iterator.hasNext()) {
ByteBuffer byteBuffer = iterator.next();
while (byteBuffer.hasRemaining()) {
this.channel.write(byteBuffer);
}
ByteBuffer byteBuffer = iterator.next();
while (byteBuffer.hasRemaining()) {
this.channel.write(byteBuffer);
}
}
this.sink.next(dataBuffer);
@@ -1215,11 +1213,6 @@ public abstract class DataBufferUtils {
failed(ex, attachment);
}
}
else {
iterator.close();
this.sink.next(dataBuffer);
request(1);
}
}
@Override
@@ -1243,6 +1236,7 @@ public abstract class DataBufferUtils {
@Override
public void completed(Integer written, Attachment attachment) {
DataBuffer.ByteBufferIterator iterator = attachment.iterator();
iterator.close();
long pos = this.position.addAndGet(written);
ByteBuffer byteBuffer = attachment.byteBuffer();
@@ -1252,11 +1246,9 @@ public abstract class DataBufferUtils {
}
else if (iterator.hasNext()) {
ByteBuffer next = iterator.next();
Attachment nextAttachment = new Attachment(next, attachment.dataBuffer(), iterator);
this.channel.write(next, pos, nextAttachment, this);
this.channel.write(next, pos, attachment, this);
}
else {
iterator.close();
this.sink.next(attachment.dataBuffer());
this.writing.set(false);
@@ -59,18 +59,6 @@ public class DefaultDeserializer implements Deserializer<Object> {
}
/**
* Return the {@link ClassLoader} to use for deserialization, or {@code null}
* to use the "latest user-defined ClassLoader".
* @since 6.2.19
* @see ConfigurableObjectInputStream#ConfigurableObjectInputStream(InputStream, ClassLoader)
*/
@Nullable
public ClassLoader getClassLoader() {
return this.classLoader;
}
/**
* Read from the supplied {@code InputStream} and deserialize the contents
* into an object.
@@ -725,7 +725,7 @@ public class AntPathMatcher implements PathMatcher {
return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str);
}
else if (this.pattern != null) {
Matcher matcher = this.pattern.matcher(new MaxAttemptsCharSequence(str));
Matcher matcher = this.pattern.matcher(str);
if (matcher.matches()) {
if (uriTemplateVariables != null) {
if (this.variableNames.size() != matcher.groupCount()) {
@@ -750,60 +750,6 @@ public class AntPathMatcher implements PathMatcher {
return false;
}
private static class MaxAttemptsCharSequence implements CharSequence {
private static final int MAX_ATTEMPTS = 1_000_000;
private final String text;
private final Counter counter;
MaxAttemptsCharSequence(String text) {
this(text, new Counter());
}
private MaxAttemptsCharSequence(String text, Counter counter) {
this.text = text;
this.counter = counter;
}
@Override
public int length() {
return this.text.length();
}
@Override
public char charAt(int index) {
if (this.counter.value++ >= MAX_ATTEMPTS) {
throw new IllegalStateException(
"Too many character access attempts encountered during pattern matching");
}
return this.text.charAt(index);
}
@Override
public boolean isEmpty() {
return this.text.isEmpty();
}
@Override
public CharSequence subSequence(int start, int end) {
return new MaxAttemptsCharSequence(this.text.substring(start, end), this.counter);
}
@Override
public String toString() {
return this.text;
}
private static class Counter {
private int value;
}
}
}
@@ -102,7 +102,7 @@ public final class ConcurrentLruCache<K, V> {
if (this.capacity == 0) {
return this.generator.apply(key);
}
Node<K, V> node = this.cache.get(key);
final Node<K, V> node = this.cache.get(key);
if (node == null) {
V value = this.generator.apply(key);
put(key, value);
@@ -115,9 +115,9 @@ public final class ConcurrentLruCache<K, V> {
private void put(K key, V value) {
Assert.notNull(key, "key must not be null");
Assert.notNull(value, "value must not be null");
CacheEntry<V> cacheEntry = new CacheEntry<>(value, CacheEntryState.ACTIVE);
Node<K, V> node = new Node<>(key, cacheEntry);
Node<K, V> prior = this.cache.putIfAbsent(node.key, node);
final CacheEntry<V> cacheEntry = new CacheEntry<>(value, CacheEntryState.ACTIVE);
final Node<K, V> node = new Node<>(key, cacheEntry);
final Node<K, V> prior = this.cache.putIfAbsent(node.key, node);
if (prior == null) {
processWrite(new AddTask(node));
}
@@ -128,7 +128,7 @@ public final class ConcurrentLruCache<K, V> {
private void processRead(Node<K, V> node) {
boolean drainRequested = this.readOperations.recordRead(node);
DrainStatus status = this.drainStatus.get();
final DrainStatus status = this.drainStatus.get();
if (status.shouldDrainBuffers(drainRequested)) {
drainOperations();
}
@@ -229,7 +229,7 @@ public final class ConcurrentLruCache<K, V> {
*/
@Nullable
public boolean remove(K key) {
Node<K, V> node = this.cache.remove(key);
final Node<K, V> node = this.cache.remove(key);
if (node == null) {
return false;
}
@@ -238,29 +238,27 @@ public final class ConcurrentLruCache<K, V> {
return true;
}
/**
/*
* Transition the node from the {@code active} state to the {@code pending removal} state,
* if the transition is valid.
*/
private void markForRemoval(Node<K, V> node) {
while (true) {
CacheEntry<V> current = node.get();
for (; ; ) {
final CacheEntry<V> current = node.get();
if (!current.isActive()) {
return;
}
CacheEntry<V> pendingRemoval = new CacheEntry<>(current.value, CacheEntryState.PENDING_REMOVAL);
final CacheEntry<V> pendingRemoval = new CacheEntry<>(current.value, CacheEntryState.PENDING_REMOVAL);
if (node.compareAndSet(current, pendingRemoval)) {
return;
}
}
}
/**
* Write operation recorded when a new entry is added to the cache.
*/
private final class AddTask implements Runnable {
final Node<K, V> node;
AddTask(Node<K, V> node) {
@@ -278,7 +276,7 @@ public final class ConcurrentLruCache<K, V> {
private void evictEntries() {
while (currentSize.get() > capacity) {
Node<K, V> node = evictionQueue.poll();
final Node<K, V> node = evictionQueue.poll();
if (node == null) {
return;
}
@@ -286,6 +284,7 @@ public final class ConcurrentLruCache<K, V> {
markAsRemoved(node);
}
}
}
@@ -293,7 +292,6 @@ public final class ConcurrentLruCache<K, V> {
* Write operation recorded when an entry is removed to the cache.
*/
private final class RemovalTask implements Runnable {
final Node<K, V> node;
RemovalTask(Node<K, V> node) {
@@ -313,7 +311,7 @@ public final class ConcurrentLruCache<K, V> {
*/
private enum DrainStatus {
/**
/*
* No drain operation currently running.
*/
IDLE {
@@ -323,7 +321,7 @@ public final class ConcurrentLruCache<K, V> {
}
},
/**
/*
* A drain operation is required due to a pending write modification.
*/
REQUIRED {
@@ -333,7 +331,7 @@ public final class ConcurrentLruCache<K, V> {
}
},
/**
/*
* A drain operation is in progress.
*/
PROCESSING {
@@ -370,6 +368,12 @@ public final class ConcurrentLruCache<K, V> {
private static final int BUFFER_COUNT = detectNumberOfBuffers();
private static int detectNumberOfBuffers() {
int availableProcessors = Runtime.getRuntime().availableProcessors();
int nextPowerOfTwo = 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(availableProcessors - 1));
return Math.min(4, nextPowerOfTwo);
}
private static final int BUFFERS_MASK = BUFFER_COUNT - 1;
private static final int MAX_PENDING_OPERATIONS = 32;
@@ -380,13 +384,19 @@ public final class ConcurrentLruCache<K, V> {
private static final int BUFFER_INDEX_MASK = BUFFER_SIZE - 1;
// Number of operations recorded, for each buffer
/*
* Number of operations recorded, for each buffer
*/
private final AtomicLongArray recordedCount = new AtomicLongArray(BUFFER_COUNT);
// Number of operations read, for each buffer
/*
* Number of operations read, for each buffer
*/
private final long[] readCount = new long[BUFFER_COUNT];
// Number of operations processed, for each buffer
/*
* Number of operations processed, for each buffer
*/
private final AtomicLongArray processedCount = new AtomicLongArray(BUFFER_COUNT);
@SuppressWarnings("rawtypes")
@@ -394,11 +404,10 @@ public final class ConcurrentLruCache<K, V> {
private final EvictionQueue<K, V> evictionQueue;
@SuppressWarnings("rawtypes")
ReadOperations(EvictionQueue<K, V> evictionQueue) {
this.evictionQueue = evictionQueue;
for (int i = 0; i < BUFFER_COUNT; i++) {
this.buffers[i] = new AtomicReferenceArray(BUFFER_SIZE);
this.buffers[i] = new AtomicReferenceArray<>(BUFFER_SIZE);
}
}
@@ -450,12 +459,6 @@ public final class ConcurrentLruCache<K, V> {
}
this.processedCount.lazySet(bufferIndex, writeCount);
}
private static int detectNumberOfBuffers() {
int availableProcessors = Runtime.getRuntime().availableProcessors();
int nextPowerOfTwo = 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(availableProcessors - 1));
return Math.min(4, nextPowerOfTwo);
}
}
@@ -541,9 +544,10 @@ public final class ConcurrentLruCache<K, V> {
if (this.first == null) {
return null;
}
Node<K, V> f = this.first;
Node<K, V> next = f.getNext();
final Node<K, V> f = this.first;
final Node<K, V> next = f.getNext();
f.setNext(null);
this.first = next;
if (next == null) {
this.last = null;
@@ -562,12 +566,13 @@ public final class ConcurrentLruCache<K, V> {
}
private boolean contains(Node<K, V> e) {
return (e.getPrevious() != null || e.getNext() != null || e == this.first);
return (e.getPrevious() != null) || (e.getNext() != null) || (e == this.first);
}
private void linkLast(Node<K, V> e) {
Node<K, V> l = this.last;
private void linkLast(final Node<K, V> e) {
final Node<K, V> l = this.last;
this.last = e;
if (l == null) {
this.first = e;
}
@@ -578,8 +583,8 @@ public final class ConcurrentLruCache<K, V> {
}
private void unlink(Node<K, V> e) {
Node<K, V> prev = e.getPrevious();
Node<K, V> next = e.getNext();
final Node<K, V> prev = e.getPrevious();
final Node<K, V> next = e.getNext();
if (prev == null) {
this.first = next;
}
@@ -517,17 +517,10 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
}
/**
* Remove any entries that have been garbage-collected and are no longer referenced.
* Note that this call implies segment locking and can lead to thread contention.
* <p>Under normal circumstances, garbage-collected entries are automatically purged as
* items are added or removed from the Map. This method can be used to force a purge
* which is useful when the Map is read frequently but hardly ever updated anymore.
* <p>Note that it may be preferable to simply {@link #clear() clear} the entire cache at
* certain points of the lifecycle, not just dropping unreferenced entries but even the
* entire cache content: assuming that most entries in the cache won't be needed anymore
* after certain processing phases, therefore rather rebuilding the cache going forward.
* @since 4.1.1
* @see #clear()
* Remove any entries that have been garbage collected and are no longer referenced.
* Under normal circumstances garbage collected entries are automatically purged as
* items are added or removed from the Map. This method can be used to force a purge,
* and is useful when the Map is read frequently but updated less often.
*/
public void purgeUnreferencedEntries() {
for (Segment segment : this.segments) {
@@ -744,7 +737,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
int currCount = this.count.get();
boolean needsResize = allowResize && (currCount > 0 && currCount >= this.resizeThreshold);
Reference<K, V> ref = this.referenceManager.pollForPurge();
if (ref != null || needsResize) {
if (ref != null || (needsResize)) {
restructure(allowResize, ref);
}
}
@@ -249,7 +249,7 @@ public abstract class MimeTypeUtils {
break;
}
}
else if (ch == '"' && mimeType.charAt(nextIndex - 1) != '\\') {
else if (ch == '"') {
quoted = !quoted;
}
nextIndex++;
@@ -59,7 +59,7 @@ class GeneratedMethodsTests {
@Test
void createWithExistingGeneratorUsesGenerator() {
Function<MethodName, String> generator = name -> "__" + name;
Function<MethodName, String> generator = name -> "__" + name.toString();
GeneratedMethods methods = new GeneratedMethods(TEST_CLASS_NAME, generator);
assertThat(methods.add("test", methodSpecCustomizer).getName()).hasToString("__test");
}
@@ -338,27 +338,6 @@ class DataBufferUtilsTests extends AbstractDataBufferAllocatingTests {
channel.close();
}
@ParameterizedDataBufferAllocatingTest
void writeWritableByteChannelWithJoinedBuffer(DataBufferFactory bufferFactory) throws Exception {
super.bufferFactory = bufferFactory;
DataBuffer foo = stringBuffer("foo");
DataBuffer bar = stringBuffer("bar");
DataBuffer joined = bufferFactory.join(List.of(foo, bar));
WritableByteChannel channel = Files.newByteChannel(tempFile, StandardOpenOption.WRITE);
Flux<DataBuffer> writeResult = DataBufferUtils.write(Flux.just(joined), channel);
StepVerifier.create(writeResult)
.consumeNextWith(stringConsumer("foobar"))
.verifyComplete();
String result = String.join("", Files.readAllLines(tempFile));
assertThat(result).isEqualTo("foobar");
channel.close();
}
@ParameterizedDataBufferAllocatingTest
void writeWritableByteChannelErrorInFlux(DataBufferFactory bufferFactory) throws Exception {
super.bufferFactory = bufferFactory;
@@ -466,27 +445,6 @@ class DataBufferUtilsTests extends AbstractDataBufferAllocatingTests {
assertThat(result).isEqualTo("foobarbazqux");
}
@ParameterizedDataBufferAllocatingTest
void writeAsynchronousFileChannelWithJoinedBuffer(DataBufferFactory bufferFactory) throws Exception {
super.bufferFactory = bufferFactory;
DataBuffer foo = stringBuffer("foo");
DataBuffer bar = stringBuffer("bar");
DataBuffer joined = bufferFactory.join(List.of(foo, bar));
AsynchronousFileChannel channel = AsynchronousFileChannel.open(tempFile, StandardOpenOption.WRITE);
Flux<DataBuffer> writeResult = DataBufferUtils.write(Flux.just(joined), channel);
StepVerifier.create(writeResult)
.consumeNextWith(stringConsumer("foobar"))
.verifyComplete();
String result = String.join("", Files.readAllLines(tempFile));
assertThat(result).isEqualTo("foobar");
channel.close();
}
@ParameterizedDataBufferAllocatingTest
void writeAsynchronousFileChannelErrorInFlux(DataBufferFactory bufferFactory) throws Exception {
super.bufferFactory = bufferFactory;
@@ -78,17 +78,6 @@ class SerializerTests {
assertThat(deserializer.expectedInputStream).isNotNull();
}
@Test // gh-36833
void defaultDeserializerExposesNullClassLoaderByDefault() {
assertThat(new DefaultDeserializer().getClassLoader()).isNull();
}
@Test // gh-36833
void defaultDeserializerExposesConfiguredClassLoader() {
ClassLoader classLoader = getClass().getClassLoader();
assertThat(new DefaultDeserializer(classLoader).getClassLoader()).isSameAs(classLoader);
}
@Test
void serializationDelegateWithExplicitSerializerAndDeserializer() throws IOException {
SerializationDelegate delegate = new SerializationDelegate(new DefaultSerializer(), new DefaultDeserializer());
@@ -98,7 +98,7 @@ class MimeTypeTests {
}
@Test
void parseQuotedParameterValue() {
void parseQuotedSeparator() {
String s = "application/xop+xml;charset=utf-8;type=\"application/soap+xml;action=\\\"https://x.y.z\\\"\"";
MimeType mimeType = MimeType.valueOf(s);
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("application");
@@ -107,15 +107,6 @@ class MimeTypeTests {
assertThat(mimeType.getParameter("type")).isEqualTo("\"application/soap+xml;action=\\\"https://x.y.z\\\"\"");
}
@Test
void parseParameterWithQuotedPair() {
String s = "text/plain;twelve=\"1\\\"2\"";
MimeType mimeType = MimeType.valueOf(s);
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("text");
assertThat(mimeType.getSubtype()).as("Invalid subtype").isEqualTo("plain");
assertThat(mimeType.getParameter("twelve")).isEqualTo("\"1\\\"2\"");
}
@Test
void withConversionService() {
ConversionService conversionService = new DefaultConversionService();
@@ -244,13 +244,6 @@ class CoroutinesUtilsTests {
Assertions.assertThat(mono.awaitSingleOrNull()).isEqualTo("foo")
}
@Test
suspend fun invokeSuspendingFunctionWithNullableValueClassParameterAndUnderlyingValue() {
val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithNullableValueClass") }
val mono = CoroutinesUtils.invokeSuspendingFunction(method, this, "foo", null) as Mono
Assertions.assertThat(mono.awaitSingleOrNull()).isEqualTo("foo")
}
@Test
fun invokeSuspendingFunctionWithNullableValueClassParameter() {
val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithNullableValueClass") }
@@ -67,8 +67,6 @@ public class ExpressionState {
@Nullable
private Deque<VariableScope> variableScopes;
private int operationsCount;
// When entering a new scope there is a new base object which should be used
// for '#this' references (or to act as a target for unqualified references).
// This ArrayDeque captures those objects at each nested scope level.
@@ -148,7 +146,6 @@ public class ExpressionState {
* @see EvaluationContext#assignVariable(String, Supplier)
*/
public TypedValue assignVariable(String name, Supplier<TypedValue> valueSupplier) {
trackOperation();
return this.relatedContext.assignVariable(name, valueSupplier);
}
@@ -173,7 +170,6 @@ public class ExpressionState {
* @see #setVariable(String, Object)
*/
public TypedValue lookupVariable(String name) {
trackOperation();
Object value = this.relatedContext.lookupVariable(name);
return (value != null ? new TypedValue(value) : TypedValue.NULL);
}
@@ -305,7 +301,6 @@ public class ExpressionState {
public TypedValue operate(Operation op, @Nullable Object left, @Nullable Object right) throws EvaluationException {
OperatorOverloader overloader = this.relatedContext.getOperatorOverloader();
if (overloader.overridesOperation(op, left, right)) {
trackOperation();
Object returnValue = overloader.operate(op, left, right);
return new TypedValue(returnValue);
}
@@ -328,18 +323,6 @@ public class ExpressionState {
return this.configuration;
}
/**
* Track an operation during expression evaluation.
* @since 6.2.19
* @see SpelParserConfiguration#getMaximumOperations()
*/
public void trackOperation() {
int maxOperations = this.configuration.getMaximumOperations();
if (++this.operationsCount >= maxOperations) {
throw new SpelEvaluationException(SpelMessage.MAX_OPERATIONS_EXCEEDED, maxOperations);
}
}
/**
* A new local variable scope is entered when a new expression scope is
@@ -303,11 +303,8 @@ public enum SpelMessage {
/** @since 6.2 */
EXCEPTION_DURING_INDEX_WRITE(Kind.ERROR, 1084,
"A problem occurred while attempting to write index ''{0}'' in ''{1}''"),
"A problem occurred while attempting to write index ''{0}'' in ''{1}''");
/** @since 6.2.19 */
MAX_OPERATIONS_EXCEEDED(Kind.ERROR, 1085,
"SpEL expression evaluation exceeded the threshold of ''{0}'' operations");
private final Kind kind;
@@ -20,8 +20,6 @@ import java.util.Locale;
import org.springframework.core.SpringProperties;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Configuration object for the SpEL expression parser.
@@ -41,38 +39,9 @@ public class SpelParserConfiguration {
*/
public static final int DEFAULT_MAX_EXPRESSION_LENGTH = 10_000;
/**
* Default maximum number of operations permitted during SpEL expression evaluation: {@value}.
* @since 6.2.19
* @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME
*/
public static final int DEFAULT_MAX_OPERATIONS = 10_000;
/**
* System property to configure the default compiler mode for SpEL expression parsers: {@value}.
* <p><strong>NOTE</strong>: Instead of relying on a global default, applications
* and frameworks should ideally set an explicit custom value via the
* {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)}
* constructor which provides complete configuration control and the ability
* to override global defaults per use case.
* <p>Can also be configured via the {@link SpringProperties} mechanism.
*/
/** System property to configure the default compiler mode for SpEL expression parsers: {@value}. */
public static final String SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME = "spring.expression.compiler.mode";
/**
* System property to configure the default maximum number of operations permitted
* during SpEL expression evaluation: {@value}.
* <p><strong>NOTE</strong>: Instead of relying on a global default, applications
* and frameworks should ideally set an explicit custom value via the
* {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)}
* constructor which provides complete configuration control and the ability
* to override global defaults per use case.
* <p>Can also be configured via the {@link SpringProperties} mechanism.
* @since 6.2.19
* @see #DEFAULT_MAX_OPERATIONS
*/
public static final String SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME = "spring.expression.maxOperations";
private static final SpelCompilerMode defaultCompilerMode;
@@ -96,17 +65,9 @@ public class SpelParserConfiguration {
private final int maximumExpressionLength;
private final int maximumOperations;
/**
* Create a new {@code SpelParserConfiguration} instance with default settings.
* <p><strong>NOTE</strong>: Favor the
* {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)}
* constructor for complete configuration control and the ability to override
* global defaults per use case.
* @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME
* @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME
*/
public SpelParserConfiguration() {
this(null, null, false, false, Integer.MAX_VALUE);
@@ -114,16 +75,8 @@ public class SpelParserConfiguration {
/**
* Create a new {@code SpelParserConfiguration} instance.
* <p><strong>NOTE</strong>: Favor the
* {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)}
* constructor for complete configuration control and the ability to override
* global defaults per use case.
* @param compilerMode the compiler mode that parsers using this configuration
* should use; or {@code null} to use the default mode
* @param compilerClassLoader the {@code ClassLoader} to use as the basis for
* expression compilation; or {@code null} to use the default {@code ClassLoader}
* @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME
* @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME
* @param compilerMode the compiler mode for the parser
* @param compilerClassLoader the ClassLoader to use as the basis for expression compilation
*/
public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader) {
this(compilerMode, compilerClassLoader, false, false, Integer.MAX_VALUE);
@@ -131,14 +84,9 @@ public class SpelParserConfiguration {
/**
* Create a new {@code SpelParserConfiguration} instance.
* <p><strong>NOTE</strong>: Favor the
* {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)}
* constructor for complete configuration control and the ability to override
* global defaults per use case.
* @param autoGrowNullReferences if null references should automatically grow
* @param autoGrowCollections if collections should automatically grow
* @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME
* @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME
* @see #SpelParserConfiguration(boolean, boolean, int)
*/
public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowCollections) {
this(null, null, autoGrowNullReferences, autoGrowCollections, Integer.MAX_VALUE);
@@ -146,15 +94,9 @@ public class SpelParserConfiguration {
/**
* Create a new {@code SpelParserConfiguration} instance.
* <p><strong>NOTE</strong>: Favor the
* {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)}
* constructor for complete configuration control and the ability to override
* global defaults per use case.
* @param autoGrowNullReferences if null references should automatically grow
* @param autoGrowCollections if collections should automatically grow
* @param maximumAutoGrowSize the maximum size to which a collection can auto grow
* @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME
* @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME
* @param maximumAutoGrowSize the maximum size that the collection can auto grow
*/
public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize) {
this(null, null, autoGrowNullReferences, autoGrowCollections, maximumAutoGrowSize);
@@ -162,19 +104,11 @@ public class SpelParserConfiguration {
/**
* Create a new {@code SpelParserConfiguration} instance.
* <p><strong>NOTE</strong>: Favor the
* {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)}
* constructor for complete configuration control and the ability to override
* global defaults per use case.
* @param compilerMode the compiler mode that parsers using this configuration
* should use; or {@code null} to use the default mode
* @param compilerClassLoader the {@code ClassLoader} to use as the basis for
* expression compilation; or {@code null} to use the default {@code ClassLoader}
* @param compilerMode the compiler mode that parsers using this configuration object should use
* @param compilerClassLoader the ClassLoader to use as the basis for expression compilation
* @param autoGrowNullReferences if null references should automatically grow
* @param autoGrowCollections if collections should automatically grow
* @param maximumAutoGrowSize the maximum size to which a collection can auto grow
* @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME
* @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME
* @param maximumAutoGrowSize the maximum size that the collection can auto grow
*/
public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader,
boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize) {
@@ -185,60 +119,24 @@ public class SpelParserConfiguration {
/**
* Create a new {@code SpelParserConfiguration} instance.
* <p><strong>NOTE</strong>: Favor the
* {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)}
* constructor for complete configuration control and the ability to override
* global defaults per use case.
* @param compilerMode the compiler mode that parsers using this configuration
* should use; or {@code null} to use the default mode
* @param compilerClassLoader the {@code ClassLoader} to use as the basis for
* expression compilation; or {@code null} to use the default {@code ClassLoader}
* @param compilerMode the compiler mode that parsers using this configuration object should use
* @param compilerClassLoader the ClassLoader to use as the basis for expression compilation
* @param autoGrowNullReferences if null references should automatically grow
* @param autoGrowCollections if collections should automatically grow
* @param maximumAutoGrowSize the maximum size to which a collection can auto grow
* @param maximumAutoGrowSize the maximum size that a collection can auto grow
* @param maximumExpressionLength the maximum length of a SpEL expression;
* must be a positive number
* @since 5.2.25
* @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME
* @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME
*/
public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader,
boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize, int maximumExpressionLength) {
this((compilerMode != null ? compilerMode : defaultCompilerMode), compilerClassLoader, autoGrowNullReferences,
autoGrowCollections, maximumAutoGrowSize, maximumExpressionLength, retrieveMaxOperations());
}
/**
* Create a new {@code SpelParserConfiguration} instance.
* @param compilerMode the compiler mode that parsers using this configuration
* should use; must not be {@code null}
* @param compilerClassLoader the {@code ClassLoader} to use as the basis for
* expression compilation; or {@code null} to use the default {@code ClassLoader}
* @param autoGrowNullReferences if null references should automatically grow
* @param autoGrowCollections if collections should automatically grow
* @param maximumAutoGrowSize the maximum size to which a collection can auto grow
* @param maximumExpressionLength the maximum length of a SpEL expression;
* must be a positive number
* @param maximumOperations the maximum number of operations permitted during
* SpEL expression evaluation; must be a positive number
* @since 6.2.19
*/
public SpelParserConfiguration(SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader,
boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize, int maximumExpressionLength,
int maximumOperations) {
Assert.notNull(compilerMode, "'compilerMode' must not be null");
Assert.isTrue(maximumExpressionLength > 0, "'maximumExpressionLength' must be a positive number");
Assert.isTrue(maximumOperations > 0, "'maximumOperations' must be a positive number");
this.compilerMode = compilerMode;
this.compilerMode = (compilerMode != null ? compilerMode : defaultCompilerMode);
this.compilerClassLoader = compilerClassLoader;
this.autoGrowNullReferences = autoGrowNullReferences;
this.autoGrowCollections = autoGrowCollections;
this.maximumAutoGrowSize = maximumAutoGrowSize;
this.maximumExpressionLength = maximumExpressionLength;
this.maximumOperations = maximumOperations;
}
@@ -250,7 +148,7 @@ public class SpelParserConfiguration {
}
/**
* Return the {@code ClassLoader} to use as the basis for expression compilation.
* Return the ClassLoader to use as the basis for expression compilation.
*/
@Nullable
public ClassLoader getCompilerClassLoader() {
@@ -272,7 +170,7 @@ public class SpelParserConfiguration {
}
/**
* Return the maximum size to which a collection can auto grow.
* Return the maximum size that a collection can auto grow.
*/
public int getMaximumAutoGrowSize() {
return this.maximumAutoGrowSize;
@@ -286,32 +184,4 @@ public class SpelParserConfiguration {
return this.maximumExpressionLength;
}
/**
* Return the maximum number of operations permitted during SpEL expression
* evaluation.
* @since 6.2.19
*/
public int getMaximumOperations() {
return this.maximumOperations;
}
private static int retrieveMaxOperations() {
String value = SpringProperties.getProperty(SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME);
if (!StringUtils.hasText(value)) {
return DEFAULT_MAX_OPERATIONS;
}
try {
int maxOperations = Integer.parseInt(value.trim());
Assert.isTrue(maxOperations > 0, () -> "Value [" + maxOperations + "] for system property [" +
SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME + "] must be positive");
return maxOperations;
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException("Failed to parse value for system property [" +
SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME + "]: " + ex.getMessage(), ex);
}
}
}
@@ -44,7 +44,6 @@ public class Assign extends SpelNodeImpl {
if (!state.getEvaluationContext().isAssignmentEnabled()) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.NOT_ASSIGNABLE, toStringAST());
}
state.trackOperation();
return this.children[0].setValueInternal(state, () -> this.children[1].getValueInternal(state));
}
@@ -65,7 +65,6 @@ public class BeanReference extends SpelNodeImpl {
getStartPosition(), SpelMessage.NO_BEAN_RESOLVER_REGISTERED, this.beanName);
}
state.trackOperation();
try {
return new TypedValue(beanResolver.resolve(state.getEvaluationContext(), this.beanName));
}
@@ -110,7 +110,6 @@ public class ConstructorReference extends SpelNodeImpl {
*/
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
state.trackOperation();
if (this.isArrayConstructor) {
return createArray(state);
}
@@ -51,7 +51,6 @@ public class Elvis extends SpelNodeImpl {
*/
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
state.trackOperation();
TypedValue value = this.children[0].getValueInternal(state);
// If this check is changed, the generateCode method will need changing too
if (value.getValue() != null && !"".equals(value.getValue())) {
@@ -84,7 +84,6 @@ public class FunctionReference extends SpelNodeImpl {
// Note: "javaMethod" cannot be named "method" due to a bug in Checkstyle.
if (function instanceof Method javaMethod) {
try {
state.trackOperation();
return executeFunctionViaMethod(state, javaMethod);
}
catch (SpelEvaluationException ex) {
@@ -96,7 +95,6 @@ public class FunctionReference extends SpelNodeImpl {
// Function registered via a MethodHandle.
if (function instanceof MethodHandle methodHandle) {
try {
state.trackOperation();
return executeFunctionViaMethodHandle(state, methodHandle);
}
catch (SpelEvaluationException ex) {
@@ -220,14 +220,14 @@ public class Indexer extends SpelNodeImpl {
if (target.getClass().isArray()) {
int intIndex = convertIndexToInt(state, index);
this.indexedType = IndexedType.ARRAY;
return new ArrayIndexingValueRef(state, state.getTypeConverter(), target, intIndex, targetDescriptor);
return new ArrayIndexingValueRef(state.getTypeConverter(), target, intIndex, targetDescriptor);
}
// Indexing into a List
if (target instanceof List<?> list) {
int intIndex = convertIndexToInt(state, index);
this.indexedType = IndexedType.LIST;
return new CollectionIndexingValueRef(state, list, intIndex, targetDescriptor,
return new CollectionIndexingValueRef(list, intIndex, targetDescriptor,
state.getTypeConverter(), state.getConfiguration().isAutoGrowCollections(),
state.getConfiguration().getMaximumAutoGrowSize());
}
@@ -240,14 +240,14 @@ public class Indexer extends SpelNodeImpl {
key = state.convertValue(key, mapKeyTypeDescriptor);
}
this.indexedType = IndexedType.MAP;
return new MapIndexingValueRef(state, state.getTypeConverter(), map, key, targetDescriptor);
return new MapIndexingValueRef(state.getTypeConverter(), map, key, targetDescriptor);
}
// Indexing into a String
if (target instanceof String string) {
int intIndex = convertIndexToInt(state, index);
this.indexedType = IndexedType.STRING;
return new StringIndexingValueRef(state, string, intIndex, targetDescriptor);
return new StringIndexingValueRef(string, intIndex, targetDescriptor);
}
// Check for a custom IndexAccessor.
@@ -259,7 +259,7 @@ public class Indexer extends SpelNodeImpl {
for (IndexAccessor indexAccessor : accessorsToTry) {
if (indexAccessor.canRead(evalContext, target, index)) {
this.indexedType = IndexedType.CUSTOM;
return new IndexAccessorValueRef(state, target, index, evalContext, targetDescriptor);
return new IndexAccessorValueRef(target, index, evalContext, targetDescriptor);
}
}
}
@@ -274,7 +274,7 @@ public class Indexer extends SpelNodeImpl {
for (IndexAccessor indexAccessor : accessorsToTry) {
if (indexAccessor.canWrite(evalContext, target, index)) {
this.indexedType = IndexedType.CUSTOM;
return new IndexAccessorValueRef(state, target, index, evalContext, targetDescriptor);
return new IndexAccessorValueRef(target, index, evalContext, targetDescriptor);
}
}
}
@@ -288,7 +288,7 @@ public class Indexer extends SpelNodeImpl {
// Fallback indexing support for collections
if (target instanceof Collection<?> collection) {
int intIndex = convertIndexToInt(state, index);
return new CollectionIndexingValueRef(state, collection, intIndex, targetDescriptor,
return new CollectionIndexingValueRef(collection, intIndex, targetDescriptor,
state.getTypeConverter(), state.getConfiguration().isAutoGrowCollections(),
state.getConfiguration().getMaximumAutoGrowSize());
}
@@ -298,7 +298,7 @@ public class Indexer extends SpelNodeImpl {
if (valueType != null && String.class == valueType.getType()) {
this.indexedType = IndexedType.OBJECT;
return new PropertyAccessorValueRef(
state, target, (String) index, state.getEvaluationContext(), targetDescriptor);
target, (String) index, state.getEvaluationContext(), targetDescriptor);
}
throw new SpelEvaluationException(
@@ -502,8 +502,6 @@ public class Indexer extends SpelNodeImpl {
private class ArrayIndexingValueRef implements ValueRef {
private final ExpressionState expressionState;
private final TypeConverter typeConverter;
private final Object array;
@@ -512,10 +510,7 @@ public class Indexer extends SpelNodeImpl {
private final TypeDescriptor typeDescriptor;
ArrayIndexingValueRef(ExpressionState expressionState, TypeConverter typeConverter,
Object array, int index, TypeDescriptor typeDescriptor) {
this.expressionState = expressionState;
ArrayIndexingValueRef(TypeConverter typeConverter, Object array, int index, TypeDescriptor typeDescriptor) {
this.typeConverter = typeConverter;
this.array = array;
this.index = index;
@@ -541,7 +536,6 @@ public class Indexer extends SpelNodeImpl {
}
private Object getArrayElement(Object ctx, int idx) throws SpelEvaluationException {
this.expressionState.trackOperation();
Class<?> arrayComponentType = ctx.getClass().componentType();
if (arrayComponentType == boolean.class) {
boolean[] array = (boolean[]) ctx;
@@ -612,7 +606,6 @@ public class Indexer extends SpelNodeImpl {
private void setArrayElement(TypeConverter converter, Object ctx, int idx, @Nullable Object newValue,
Class<?> arrayComponentType) throws EvaluationException {
this.expressionState.trackOperation();
if (arrayComponentType == boolean.class) {
boolean[] array = (boolean[]) ctx;
checkAccess(array.length, idx);
@@ -676,14 +669,13 @@ public class Indexer extends SpelNodeImpl {
}
return result;
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
private class MapIndexingValueRef implements ValueRef {
private final ExpressionState expressionState;
private final TypeConverter typeConverter;
private final Map map;
@@ -693,10 +685,9 @@ public class Indexer extends SpelNodeImpl {
private final TypeDescriptor mapEntryDescriptor;
public MapIndexingValueRef(ExpressionState expressionState, TypeConverter typeConverter,
Map map, @Nullable Object key, TypeDescriptor mapEntryDescriptor) {
public MapIndexingValueRef(
TypeConverter typeConverter, Map map, @Nullable Object key, TypeDescriptor mapEntryDescriptor) {
this.expressionState = expressionState;
this.typeConverter = typeConverter;
this.map = map;
this.key = key;
@@ -705,7 +696,6 @@ public class Indexer extends SpelNodeImpl {
@Override
public TypedValue getValue() {
this.expressionState.trackOperation();
Object value = this.map.get(this.key);
exitTypeDescriptor = CodeFlow.toDescriptor(Object.class);
return new TypedValue(value, this.mapEntryDescriptor.getMapValueTypeDescriptor(value));
@@ -717,7 +707,6 @@ public class Indexer extends SpelNodeImpl {
newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue),
this.mapEntryDescriptor.getMapValueTypeDescriptor());
}
this.expressionState.trackOperation();
this.map.put(this.key, newValue);
}
@@ -730,8 +719,6 @@ public class Indexer extends SpelNodeImpl {
private class PropertyAccessorValueRef implements ValueRef {
private final ExpressionState expressionState;
private final Object targetObject;
private final String name;
@@ -740,10 +727,9 @@ public class Indexer extends SpelNodeImpl {
private final TypeDescriptor targetObjectTypeDescriptor;
public PropertyAccessorValueRef(ExpressionState expressionState, Object targetObject, String name,
public PropertyAccessorValueRef(Object targetObject, String name,
EvaluationContext evaluationContext, TypeDescriptor targetObjectTypeDescriptor) {
this.expressionState = expressionState;
this.targetObject = targetObject;
this.name = name;
this.evaluationContext = evaluationContext;
@@ -761,7 +747,6 @@ public class Indexer extends SpelNodeImpl {
// Is it OK to use the cached accessor?
if (cachedPropertyName.equals(this.name) && cachedTargetType.equals(targetType)) {
PropertyAccessor accessor = cachedPropertyReadState.accessor;
this.expressionState.trackOperation();
return accessor.read(this.evaluationContext, this.targetObject, this.name);
}
// If the above code block did not use a cached accessor and return a value,
@@ -776,7 +761,6 @@ public class Indexer extends SpelNodeImpl {
accessor = reflectivePropertyAccessor.createOptimalAccessor(
this.evaluationContext, this.targetObject, this.name);
}
this.expressionState.trackOperation();
TypedValue result = accessor.read(this.evaluationContext, this.targetObject, this.name);
Indexer.this.cachedPropertyReadState = new CachedPropertyState(accessor, targetType, this.name);
if (accessor instanceof CompilablePropertyAccessor compilablePropertyAccessor) {
@@ -805,7 +789,6 @@ public class Indexer extends SpelNodeImpl {
// Is it OK to use the cached accessor?
if (cachedPropertyName.equals(this.name) && cachedTargetType.equals(targetType)) {
PropertyAccessor accessor = cachedPropertyWriteState.accessor;
this.expressionState.trackOperation();
accessor.write(this.evaluationContext, this.targetObject, this.name, newValue);
return;
}
@@ -817,7 +800,6 @@ public class Indexer extends SpelNodeImpl {
AccessorUtils.getAccessorsToTry(targetType, this.evaluationContext.getPropertyAccessors());
for (PropertyAccessor accessor : accessorsToTry) {
if (accessor.canWrite(this.evaluationContext, this.targetObject, this.name)) {
this.expressionState.trackOperation();
accessor.write(this.evaluationContext, this.targetObject, this.name, newValue);
Indexer.this.cachedPropertyWriteState = new CachedPropertyState(accessor, targetType, this.name);
return;
@@ -842,8 +824,6 @@ public class Indexer extends SpelNodeImpl {
@SuppressWarnings({"rawtypes", "unchecked"})
private class CollectionIndexingValueRef implements ValueRef {
private final ExpressionState expressionState;
private final Collection collection;
private final int index;
@@ -856,11 +836,9 @@ public class Indexer extends SpelNodeImpl {
private final int maximumSize;
public CollectionIndexingValueRef(ExpressionState expressionState, Collection collection, int index,
TypeDescriptor collectionEntryDescriptor, TypeConverter typeConverter,
boolean growCollection, int maximumSize) {
public CollectionIndexingValueRef(Collection collection, int index, TypeDescriptor collectionEntryDescriptor,
TypeConverter typeConverter, boolean growCollection, int maximumSize) {
this.expressionState = expressionState;
this.collection = collection;
this.index = index;
this.collectionEntryDescriptor = collectionEntryDescriptor;
@@ -873,7 +851,6 @@ public class Indexer extends SpelNodeImpl {
public TypedValue getValue() {
growCollectionIfNecessary();
if (this.collection instanceof List list) {
this.expressionState.trackOperation();
Object o = list.get(this.index);
exitTypeDescriptor = CodeFlow.toDescriptor(Object.class);
return new TypedValue(o, this.collectionEntryDescriptor.elementTypeDescriptor(o));
@@ -881,7 +858,6 @@ public class Indexer extends SpelNodeImpl {
int pos = 0;
for (Object o : this.collection) {
this.expressionState.trackOperation();
if (pos == this.index) {
return new TypedValue(o, this.collectionEntryDescriptor.elementTypeDescriptor(o));
}
@@ -903,7 +879,6 @@ public class Indexer extends SpelNodeImpl {
newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue),
this.collectionEntryDescriptor.getElementTypeDescriptor());
}
this.expressionState.trackOperation();
list.set(this.index, newValue);
}
@@ -925,7 +900,6 @@ public class Indexer extends SpelNodeImpl {
Constructor<?> ctor = getDefaultConstructor(elementType.getType());
int newElements = this.index - this.collection.size();
while (newElements >= 0) {
this.expressionState.trackOperation();
// Insert a null value if the element type does not have a default constructor.
this.collection.add(ctor != null ? ctor.newInstance() : null);
newElements--;
@@ -956,18 +930,13 @@ public class Indexer extends SpelNodeImpl {
private class StringIndexingValueRef implements ValueRef {
private final ExpressionState expressionState;
private final String target;
private final int index;
private final TypeDescriptor typeDescriptor;
public StringIndexingValueRef(
ExpressionState expressionState, String target, int index, TypeDescriptor typeDescriptor) {
this.expressionState = expressionState;
public StringIndexingValueRef(String target, int index, TypeDescriptor typeDescriptor) {
this.target = target;
this.index = index;
this.typeDescriptor = typeDescriptor;
@@ -979,7 +948,6 @@ public class Indexer extends SpelNodeImpl {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.STRING_INDEX_OUT_OF_BOUNDS,
this.target.length(), this.index);
}
this.expressionState.trackOperation();
return new TypedValue(String.valueOf(this.target.charAt(this.index)));
}
@@ -998,8 +966,6 @@ public class Indexer extends SpelNodeImpl {
private class IndexAccessorValueRef implements ValueRef {
private final ExpressionState expressionState;
private final Object target;
private final Object index;
@@ -1009,10 +975,9 @@ public class Indexer extends SpelNodeImpl {
private final TypeDescriptor typeDescriptor;
IndexAccessorValueRef(ExpressionState expressionState, Object target, Object index,
EvaluationContext evaluationContext, TypeDescriptor typeDescriptor) {
IndexAccessorValueRef(Object target, Object index, EvaluationContext evaluationContext,
TypeDescriptor typeDescriptor) {
this.expressionState = expressionState;
this.target = target;
this.index = index;
this.evaluationContext = evaluationContext;
@@ -1034,7 +999,6 @@ public class Indexer extends SpelNodeImpl {
IndexAccessor accessor = cachedIndexReadState.accessor;
if (this.evaluationContext.getIndexAccessors().contains(accessor)) {
try {
this.expressionState.trackOperation();
return accessor.read(this.evaluationContext, this.target, this.index);
}
catch (Exception ex) {
@@ -1053,7 +1017,6 @@ public class Indexer extends SpelNodeImpl {
AccessorUtils.getAccessorsToTry(this.target, this.evaluationContext.getIndexAccessors());
for (IndexAccessor indexAccessor : accessorsToTry) {
if (indexAccessor.canRead(this.evaluationContext, this.target, this.index)) {
this.expressionState.trackOperation();
TypedValue result = indexAccessor.read(this.evaluationContext, this.target, this.index);
Indexer.this.cachedIndexReadState = new CachedIndexState(indexAccessor, targetType, this.index);
if (indexAccessor instanceof CompilableIndexAccessor compilableIndexAccessor) {
@@ -1090,7 +1053,6 @@ public class Indexer extends SpelNodeImpl {
IndexAccessor accessor = cachedIndexWriteState.accessor;
if (this.evaluationContext.getIndexAccessors().contains(accessor)) {
try {
this.expressionState.trackOperation();
accessor.write(this.evaluationContext, this.target, this.index, newValue);
return;
}
@@ -1110,7 +1072,6 @@ public class Indexer extends SpelNodeImpl {
AccessorUtils.getAccessorsToTry(this.target, this.evaluationContext.getIndexAccessors());
for (IndexAccessor indexAccessor : accessorsToTry) {
if (indexAccessor.canWrite(this.evaluationContext, this.target, this.index)) {
this.expressionState.trackOperation();
indexAccessor.write(this.evaluationContext, this.target, this.index, newValue);
Indexer.this.cachedIndexWriteState = new CachedIndexState(indexAccessor, targetType, this.index);
return;
@@ -27,7 +27,7 @@ import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -42,64 +42,69 @@ import org.springframework.util.Assert;
*/
public class InlineList extends SpelNodeImpl {
private final boolean isConstant;
@Nullable
private volatile TypedValue constant;
private final TypedValue constant;
public InlineList(int startPos, int endPos, SpelNodeImpl... args) {
super(startPos, endPos, args);
this.isConstant = determineIfConstant();
this.constant = computeConstantValue();
}
/**
* Determine whether this list is structurally eligible to be a constant
* value: whether all of its components are themselves constants or lists
* that contain only constants.
* <p>The actual constant value is created lazily on the first call to
* {@link #getValueInternal(ExpressionState)}.
* If all the components of the list are constants, or lists that themselves
* contain constants, then a constant list can be built to represent this node.
* <p>This will speed up later getValue calls and reduce the amount of garbage
* created.
*/
private boolean determineIfConstant() {
@Nullable
private TypedValue computeConstantValue() {
for (int c = 0, max = getChildCount(); c < max; c++) {
SpelNode child = getChild(c);
if (child instanceof Literal) {
continue;
if (!(child instanceof Literal)) {
if (child instanceof InlineList inlineList) {
if (!inlineList.isConstant()) {
return null;
}
}
else if (!(child instanceof OpMinus opMinus) || !opMinus.isNegativeNumberLiteral()) {
return null;
}
}
if (child instanceof InlineList inlineList && inlineList.isConstant()) {
continue;
}
if (child instanceof OpMinus opMinus && opMinus.isNegativeNumberLiteral()) {
continue;
}
return false;
}
return true;
List<Object> constantList = new ArrayList<>();
int childcount = getChildCount();
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
for (int c = 0; c < childcount; c++) {
SpelNode child = getChild(c);
if (child instanceof Literal literal) {
constantList.add(literal.getLiteralValue().getValue());
}
else if (child instanceof InlineList inlineList) {
constantList.add(inlineList.getConstantValue());
}
else if (child instanceof OpMinus) {
constantList.add(child.getValue(expressionState));
}
}
return new TypedValue(Collections.unmodifiableList(constantList));
}
@Override
public TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException {
TypedValue result = this.constant;
if (result != null) {
return result;
if (this.constant != null) {
return this.constant;
}
result = createList(expressionState);
if (this.isConstant) {
this.constant = result;
else {
int childCount = getChildCount();
List<Object> returnValue = new ArrayList<>(childCount);
for (int c = 0; c < childCount; c++) {
returnValue.add(getChild(c).getValue(expressionState));
}
return new TypedValue(returnValue);
}
return result;
}
private TypedValue createList(ExpressionState expressionState) throws EvaluationException {
int childCount = getChildCount();
expressionState.trackOperation();
List<Object> list = new ArrayList<>(childCount);
for (int c = 0; c < childCount; c++) {
expressionState.trackOperation();
list.add(getChild(c).getValue(expressionState));
}
return new TypedValue(this.isConstant ? Collections.unmodifiableList(list) : list);
}
@Override
@@ -113,38 +118,22 @@ public class InlineList extends SpelNodeImpl {
}
/**
* Return whether this list is structurally a constant value.
* <p>Note that the resulting constant value is created lazily on the
* first call to {@link #getValueInternal(ExpressionState)} or
* {@link #getConstantValue()}.
* Return whether this list is a constant value.
*/
public boolean isConstant() {
return this.isConstant;
return (this.constant != null);
}
/**
* Return the cached constant {@link List} value for this inline list,
* lazily creating it on first access.
* @see #isConstant()
* @deprecated as of Spring Framework 6.2.19; this method was only intended for
* testing purposes and will be removed in a future version of the framework
*/
@SuppressWarnings("unchecked")
@Deprecated(since = "6.2.19")
@Nullable
public List<Object> getConstantValue() {
Assert.state(this.isConstant, "Not a constant");
TypedValue result = this.constant;
if (result == null) {
result = createList(new ExpressionState(SimpleEvaluationContext.forReadOnlyDataBinding().build()));
this.constant = result;
}
return (List<Object>) result.getValue();
Assert.state(this.constant != null, "No constant");
return (List<Object>) this.constant.getValue();
}
@Override
public boolean isCompilable() {
return this.isConstant;
return isConstant();
}
@Override
@@ -24,7 +24,7 @@ import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -39,80 +39,105 @@ import org.springframework.util.Assert;
*/
public class InlineMap extends SpelNodeImpl {
private final boolean isConstant;
@Nullable
private volatile TypedValue constant;
private final TypedValue constant;
public InlineMap(int startPos, int endPos, SpelNodeImpl... args) {
super(startPos, endPos, args);
this.isConstant = determineIfConstant();
this.constant = computeConstantValue();
}
/**
* Determine whether this map is structurally eligible to be a constant
* value: whether all of its components are themselves constants or lists/maps
* that contain only constants.
* <p>The actual constant value is created lazily on the first call to
* {@link #getValueInternal(ExpressionState)}.
* If all the components of the map are constants, or lists/maps that themselves
* contain constants, then a constant map can be built to represent this node.
* <p>This will speed up later getValue calls and reduce the amount of garbage
* created.
*/
private boolean determineIfConstant() {
int childCount = getChildCount();
for (int c = 0; c < childCount; c++) {
@Nullable
private TypedValue computeConstantValue() {
for (int c = 0, max = getChildCount(); c < max; c++) {
SpelNode child = getChild(c);
if (child instanceof Literal) {
continue;
if (!(child instanceof Literal)) {
if (child instanceof InlineList inlineList) {
if (!inlineList.isConstant()) {
return null;
}
}
else if (child instanceof InlineMap inlineMap) {
if (!inlineMap.isConstant()) {
return null;
}
}
else if (!(c % 2 == 0 && child instanceof PropertyOrFieldReference)) {
if (!(child instanceof OpMinus opMinus) || !opMinus.isNegativeNumberLiteral()) {
return null;
}
}
}
if (child instanceof InlineList inlineList && inlineList.isConstant()) {
continue;
}
if (child instanceof InlineMap inlineMap && inlineMap.isConstant()) {
continue;
}
if (c % 2 == 0 && child instanceof PropertyOrFieldReference) {
continue;
}
if (child instanceof OpMinus opMinus && opMinus.isNegativeNumberLiteral()) {
continue;
}
return false;
}
return true;
Map<Object, Object> constantMap = new LinkedHashMap<>();
int childCount = getChildCount();
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
for (int c = 0; c < childCount; c++) {
SpelNode keyChild = getChild(c++);
Object key;
if (keyChild instanceof Literal literal) {
key = literal.getLiteralValue().getValue();
}
else if (keyChild instanceof PropertyOrFieldReference propertyOrFieldReference) {
key = propertyOrFieldReference.getName();
}
else if (keyChild instanceof OpMinus) {
key = keyChild.getValue(expressionState);
}
else {
return null;
}
SpelNode valueChild = getChild(c);
Object value = null;
if (valueChild instanceof Literal literal) {
value = literal.getLiteralValue().getValue();
}
else if (valueChild instanceof InlineList inlineList) {
value = inlineList.getConstantValue();
}
else if (valueChild instanceof InlineMap inlineMap) {
value = inlineMap.getConstantValue();
}
else if (valueChild instanceof OpMinus) {
value = valueChild.getValue(expressionState);
}
constantMap.put(key, value);
}
return new TypedValue(Collections.unmodifiableMap(constantMap));
}
@Override
public TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException {
TypedValue result = this.constant;
if (result != null) {
return result;
if (this.constant != null) {
return this.constant;
}
result = createMap(expressionState);
if (this.isConstant) {
this.constant = result;
}
return result;
}
private TypedValue createMap(ExpressionState expressionState) throws EvaluationException {
expressionState.trackOperation();
Map<Object, Object> map = new LinkedHashMap<>();
int childCount = getChildCount();
for (int c = 0; c < childCount; c++) {
SpelNode keyChild = getChild(c++);
Object key;
if (keyChild instanceof PropertyOrFieldReference reference) {
key = reference.getName();
else {
Map<Object, Object> returnValue = new LinkedHashMap<>();
int childcount = getChildCount();
for (int c = 0; c < childcount; c++) {
SpelNode keyChild = getChild(c++);
Object key = null;
if (keyChild instanceof PropertyOrFieldReference reference) {
key = reference.getName();
}
else {
key = keyChild.getValue(expressionState);
}
Object value = getChild(c).getValue(expressionState);
returnValue.put(key, value);
}
else {
key = keyChild.getValue(expressionState);
}
Object value = getChild(c).getValue(expressionState);
expressionState.trackOperation();
map.put(key, value);
return new TypedValue(returnValue);
}
return new TypedValue(this.isConstant ? Collections.unmodifiableMap(map) : map);
}
@Override
@@ -131,33 +156,17 @@ public class InlineMap extends SpelNodeImpl {
}
/**
* Return whether this map is structurally a constant value.
* <p>Note that the resulting constant value is created lazily on the
* first call to {@link #getValueInternal(ExpressionState)} or
* {@link #getConstantValue()}.
* Return whether this map is a constant value.
*/
public boolean isConstant() {
return this.isConstant;
return this.constant != null;
}
/**
* Return the cached constant {@link Map} value for this inline map,
* lazily creating it on first access.
* @see #isConstant()
* @deprecated as of Spring Framework 6.2.19; this method was only intended for
* testing purposes and will be removed in a future version of the framework
*/
@SuppressWarnings("unchecked")
@Deprecated(since = "6.2.19")
@Nullable
public Map<Object, Object> getConstantValue() {
Assert.state(this.isConstant, "Not a constant");
TypedValue result = this.constant;
if (result == null) {
result = createMap(new ExpressionState(SimpleEvaluationContext.forReadOnlyDataBinding().build()));
this.constant = result;
}
return (Map<Object, Object>) result.getValue();
Assert.state(this.constant != null, "No constant");
return (Map<Object, Object>) this.constant.getValue();
}
}
@@ -105,12 +105,12 @@ public class MethodReference extends SpelNodeImpl {
Object value = state.getActiveContextObject().getValue();
TypeDescriptor targetType = state.getActiveContextObject().getTypeDescriptor();
Object[] arguments = getArguments(state);
TypedValue result = getValueInternal(state, evaluationContext, value, targetType, arguments);
TypedValue result = getValueInternal(evaluationContext, value, targetType, arguments);
updateExitTypeDescriptor();
return result;
}
private TypedValue getValueInternal(ExpressionState state, EvaluationContext evaluationContext,
private TypedValue getValueInternal(EvaluationContext evaluationContext,
@Nullable Object value, @Nullable TypeDescriptor targetType, Object[] arguments) {
List<TypeDescriptor> argumentTypes = getArgumentTypes(arguments);
@@ -122,7 +122,6 @@ public class MethodReference extends SpelNodeImpl {
MethodExecutor executorToUse = getCachedExecutor(evaluationContext, value, targetType, argumentTypes);
if (executorToUse != null) {
try {
state.trackOperation();
return executorToUse.execute(evaluationContext, value, arguments);
}
catch (AccessException ex) {
@@ -150,7 +149,6 @@ public class MethodReference extends SpelNodeImpl {
this.cachedExecutor = new CachedMethodExecutor(
executorToUse, (value instanceof Class<?> clazz ? clazz : null), targetType, argumentTypes);
try {
state.trackOperation();
return executorToUse.execute(evaluationContext, value, arguments);
}
catch (AccessException ex) {
@@ -378,8 +376,6 @@ public class MethodReference extends SpelNodeImpl {
private class MethodValueRef implements ValueRef {
private final ExpressionState expressionState;
private final EvaluationContext evaluationContext;
@Nullable
@@ -391,7 +387,6 @@ public class MethodReference extends SpelNodeImpl {
private final Object[] arguments;
public MethodValueRef(ExpressionState state, Object[] arguments) {
this.expressionState = state;
this.evaluationContext = state.getEvaluationContext();
this.value = state.getActiveContextObject().getValue();
this.targetType = state.getActiveContextObject().getTypeDescriptor();
@@ -401,7 +396,7 @@ public class MethodReference extends SpelNodeImpl {
@Override
public TypedValue getValue() {
TypedValue result = MethodReference.this.getValueInternal(
this.expressionState, this.evaluationContext, this.value, this.targetType, this.arguments);
this.evaluationContext, this.value, this.targetType, this.arguments);
updateExitTypeDescriptor();
return result;
}
@@ -46,7 +46,6 @@ public class OpAnd extends Operator {
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
state.trackOperation();
if (!getBooleanValue(state, getLeftOperand())) {
// no need to evaluate right operand
return BooleanTypedValue.FALSE;
@@ -68,7 +68,6 @@ public class OpDec extends Operator {
TypedValue newValue = null;
if (operandValue instanceof Number op1) {
state.trackOperation();
if (op1 instanceof BigDecimal bigDecimal) {
newValue = new TypedValue(bigDecimal.subtract(BigDecimal.ONE), operandTypedValue.getTypeDescriptor());
}
@@ -115,9 +114,8 @@ public class OpDec extends Operator {
}
}
state.trackOperation();
// set the new value
try {
// set the new value
lvalue.setValue(newValue.getValue());
}
catch (SpelEvaluationException see) {
@@ -51,7 +51,6 @@ public class OpDivide extends Operator {
Object rightOperand = getRightOperand().getValueInternal(state).getValue();
if (leftOperand instanceof Number leftNumber && rightOperand instanceof Number rightNumber) {
state.trackOperation();
if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) {
BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
@@ -43,7 +43,6 @@ public class OpEQ extends Operator {
Object right = getRightOperand().getValueInternal(state).getValue();
this.leftActualDescriptor = CodeFlow.toDescriptorFromObject(left);
this.rightActualDescriptor = CodeFlow.toDescriptorFromObject(right);
state.trackOperation();
return BooleanTypedValue.forValue(equalityCheck(state.getEvaluationContext(), left, right));
}
@@ -46,7 +46,6 @@ public class OpGE extends Operator {
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object left = getLeftOperand().getValueInternal(state).getValue();
Object right = getRightOperand().getValueInternal(state).getValue();
state.trackOperation();
this.leftActualDescriptor = CodeFlow.toDescriptorFromObject(left);
this.rightActualDescriptor = CodeFlow.toDescriptorFromObject(right);
@@ -46,7 +46,6 @@ public class OpGT extends Operator {
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object left = getLeftOperand().getValueInternal(state).getValue();
Object right = getRightOperand().getValueInternal(state).getValue();
state.trackOperation();
this.leftActualDescriptor = CodeFlow.toDescriptorFromObject(left);
this.rightActualDescriptor = CodeFlow.toDescriptorFromObject(right);
@@ -66,7 +66,6 @@ public class OpInc extends Operator {
TypedValue newValue = null;
if (value instanceof Number op1) {
state.trackOperation();
if (op1 instanceof BigDecimal bigDecimal) {
newValue = new TypedValue(bigDecimal.add(BigDecimal.ONE), typedValue.getTypeDescriptor());
}
@@ -111,9 +110,8 @@ public class OpInc extends Operator {
}
}
state.trackOperation();
// set the new value
try {
// set the new value
valueRef.setValue(newValue.getValue());
}
catch (SpelEvaluationException see) {
@@ -46,7 +46,6 @@ public class OpLE extends Operator {
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object left = getLeftOperand().getValueInternal(state).getValue();
Object right = getRightOperand().getValueInternal(state).getValue();
state.trackOperation();
this.leftActualDescriptor = CodeFlow.toDescriptorFromObject(left);
this.rightActualDescriptor = CodeFlow.toDescriptorFromObject(right);
@@ -46,7 +46,6 @@ public class OpLT extends Operator {
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object left = getLeftOperand().getValueInternal(state).getValue();
Object right = getRightOperand().getValueInternal(state).getValue();
state.trackOperation();
this.leftActualDescriptor = CodeFlow.toDescriptorFromObject(left);
this.rightActualDescriptor = CodeFlow.toDescriptorFromObject(right);
@@ -51,13 +51,6 @@ public class OpMinus extends Operator {
public OpMinus(int startPos, int endPos, SpelNodeImpl... operands) {
super("-", startPos, endPos, operands);
// If this is a unary negation of a number literal, the exit type descriptor
// can be derived statically from the literal. Doing so up front lets the
// compiler determine that this node is compilable without first evaluating
// the expression.
if (isNegativeNumberLiteral()) {
this.exitTypeDescriptor = ((Literal) operands[0]).exitTypeDescriptor;
}
}
@@ -79,7 +72,6 @@ public class OpMinus extends Operator {
if (this.children.length < 2) { // if only one operand, then this is unary minus
Object operand = leftOp.getValueInternal(state).getValue();
if (operand instanceof Number number) {
state.trackOperation();
if (number instanceof BigDecimal bigDecimal) {
return new TypedValue(bigDecimal.negate());
}
@@ -120,7 +112,6 @@ public class OpMinus extends Operator {
Object right = getRightOperand().getValueInternal(state).getValue();
if (left instanceof Number leftNumber && right instanceof Number rightNumber) {
state.trackOperation();
if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) {
BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
@@ -154,7 +145,6 @@ public class OpMinus extends Operator {
}
if (left instanceof String theString && right instanceof Integer theInteger && theString.length() == 1) {
state.trackOperation();
// Implements character - int (ie. b - 1 = a)
return new TypedValue(Character.toString((char) (theString.charAt(0) - theInteger)));
}
@@ -49,7 +49,6 @@ public class OpModulus extends Operator {
Object rightOperand = getRightOperand().getValueInternal(state).getValue();
if (leftOperand instanceof Number leftNumber && rightOperand instanceof Number rightNumber) {
state.trackOperation();
if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) {
BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
@@ -82,7 +82,6 @@ public class OpMultiply extends Operator {
Object rightOperand = getRightOperand().getValueInternal(state).getValue();
if (leftOperand instanceof Number leftNumber && rightOperand instanceof Number rightNumber) {
state.trackOperation();
if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) {
BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
@@ -117,7 +116,6 @@ public class OpMultiply extends Operator {
if (leftOperand instanceof String text && rightOperand instanceof Integer count) {
checkRepeatedTextSize(text, count);
state.trackOperation();
return new TypedValue(text.repeat(count));
}
@@ -129,9 +127,9 @@ public class OpMultiply extends Operator {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.NEGATIVE_REPEATED_TEXT_COUNT, count);
}
long result = (long) text.length() * (long) count;
int result = text.length() * count;
if (result < 0 || result > MAX_REPEATED_TEXT_SIZE) {
throw new SpelEvaluationException(getRightOperand().getStartPosition(),
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED, MAX_REPEATED_TEXT_SIZE);
}
}
@@ -44,7 +44,6 @@ public class OpNE extends Operator {
Object rightValue = getRightOperand().getValueInternal(state).getValue();
this.leftActualDescriptor = CodeFlow.toDescriptorFromObject(leftValue);
this.rightActualDescriptor = CodeFlow.toDescriptorFromObject(rightValue);
state.trackOperation();
return BooleanTypedValue.forValue(!equalityCheck(state.getEvaluationContext(), leftValue, rightValue));
}
@@ -45,7 +45,6 @@ public class OpOr extends Operator {
@Override
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
state.trackOperation();
if (getBooleanValue(state, getLeftOperand())) {
// no need to evaluate right operand
return BooleanTypedValue.TRUE;
@@ -73,7 +73,6 @@ public class OpPlus extends Operator {
if (this.children.length < 2) { // if only one operand, then this is unary plus
Object operandOne = leftOp.getValueInternal(state).getValue();
if (operandOne instanceof Number) {
state.trackOperation();
if (operandOne instanceof Double) {
this.exitTypeDescriptor = "D";
}
@@ -97,7 +96,6 @@ public class OpPlus extends Operator {
Object rightOperand = operandTwoValue.getValue();
if (leftOperand instanceof Number leftNumber && rightOperand instanceof Number rightNumber) {
state.trackOperation();
if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) {
BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
@@ -134,41 +132,37 @@ public class OpPlus extends Operator {
this.exitTypeDescriptor = "Ljava/lang/String";
checkStringLength(leftString);
checkStringLength(rightString);
return concatenate(state, leftString, rightString);
return concatenate(leftString, rightString);
}
if (leftOperand instanceof String leftString) {
checkStringLength(leftString);
String rightString = (rightOperand == null ? "null" : convertTypedValueToString(operandTwoValue, state));
checkStringLength(rightString);
return concatenate(state, leftString, rightString);
return concatenate(leftString, rightString);
}
if (rightOperand instanceof String rightString) {
checkStringLength(rightString);
String leftString = (leftOperand == null ? "null" : convertTypedValueToString(operandOneValue, state));
checkStringLength(leftString);
return concatenate(state, leftString, rightString);
return concatenate(leftString, rightString);
}
return state.operate(Operation.ADD, leftOperand, rightOperand);
}
private void checkStringLength(String string) {
checkStringLength(string.length());
}
private void checkStringLength(int stringLength) {
if (stringLength > MAX_CONCATENATED_STRING_LENGTH) {
if (string.length() > MAX_CONCATENATED_STRING_LENGTH) {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, MAX_CONCATENATED_STRING_LENGTH);
}
}
private TypedValue concatenate(ExpressionState state, String leftString, String rightString) {
checkStringLength(leftString.length() + rightString.length());
state.trackOperation();
return new TypedValue(leftString + rightString);
private TypedValue concatenate(String leftString, String rightString) {
String result = leftString + rightString;
checkStringLength(result);
return new TypedValue(result);
}
@Override
@@ -68,7 +68,6 @@ public class OperatorBetween extends Operator {
Object high = list.get(1);
TypeComparator comp = state.getTypeComparator();
try {
state.trackOperation();
return BooleanTypedValue.forValue(comp.compare(left, low) >= 0 && comp.compare(left, high) <= 0);
}
catch (SpelEvaluationException ex) {
@@ -62,7 +62,6 @@ public class OperatorInstanceof extends Operator {
Object leftValue = left.getValue();
Object rightValue = right.getValue();
BooleanTypedValue result;
state.trackOperation();
if (!(rightValue instanceof Class<?> rightClass)) {
throw new SpelEvaluationException(getRightOperand().getStartPosition(),
SpelMessage.INSTANCEOF_OPERATOR_NEEDS_CLASS_OPERAND,
@@ -16,6 +16,7 @@
package org.springframework.expression.spel.ast;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -26,7 +27,6 @@ import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.BooleanTypedValue;
import org.springframework.util.ConcurrentLruCache;
/**
* Implements the matches operator. Matches takes two operands:
@@ -41,12 +41,6 @@ import org.springframework.util.ConcurrentLruCache;
*/
public class OperatorMatches extends Operator {
/**
* Maximum number of compiled regular expressions in the pattern cache: {@value}.
* @since 6.2.19
*/
public static final int MAX_PATTERN_CACHE_SIZE = 256;
private static final int PATTERN_ACCESS_THRESHOLD = 1000000;
/**
@@ -55,47 +49,29 @@ public class OperatorMatches extends Operator {
*/
private static final int MAX_REGEX_LENGTH = 1000;
private final ConcurrentLruCache<String, Pattern> patternCache;
private final ConcurrentMap<String, Pattern> patternCache;
/**
* Create a new {@link OperatorMatches} instance.
* @deprecated as of Spring Framework 5.2.23; for removal in Spring Framework 7.1; invoke
* {@link #OperatorMatches(ConcurrentLruCache, int, int, SpelNodeImpl...)} instead
* @deprecated as of Spring Framework 5.2.23 in favor of invoking
* {@link #OperatorMatches(ConcurrentMap, int, int, SpelNodeImpl...)}
* with a shared pattern cache instead
*/
@Deprecated(since = "5.2.23", forRemoval = true)
@Deprecated(since = "5.2.23")
public OperatorMatches(int startPos, int endPos, SpelNodeImpl... operands) {
this(new ConcurrentLruCache<>(MAX_PATTERN_CACHE_SIZE, Pattern::compile), startPos, endPos, operands);
this(new ConcurrentHashMap<>(), startPos, endPos, operands);
}
/**
* Create a new {@link OperatorMatches} instance with a shared pattern cache.
* <p>As of Spring Framework 6.2.19, the supplied {@code patternCacheMap} will
* be ignored.
* @since 5.2.23
* @deprecated as of Spring Framework 6.2.19; for removal in Spring Framework 7.1; invoke
* {@link #OperatorMatches(ConcurrentLruCache, int, int, SpelNodeImpl...)} instead
*/
@Deprecated(since = "6.2.19", forRemoval = true)
public OperatorMatches(ConcurrentMap<String, Pattern> patternCacheMap,
int startPos, int endPos, SpelNodeImpl... operands) {
this(startPos, endPos, operands);
}
/**
* Create a new {@link OperatorMatches} instance with a shared pattern cache.
* @since 6.2.19
*/
public OperatorMatches(ConcurrentLruCache<String, Pattern> patternCache,
int startPos, int endPos, SpelNodeImpl... operands) {
public OperatorMatches(ConcurrentMap<String, Pattern> patternCache, int startPos, int endPos, SpelNodeImpl... operands) {
super("matches", startPos, endPos, operands);
this.patternCache = patternCache;
}
/**
* Check the first operand matches the regex specified as the second operand.
* @param state the expression state
@@ -120,14 +96,14 @@ public class OperatorMatches extends Operator {
throw new SpelEvaluationException(rightOp.getStartPosition(),
SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, right);
}
if (regex.length() > MAX_REGEX_LENGTH) {
throw new SpelEvaluationException(rightOp.getStartPosition(),
SpelMessage.MAX_REGEX_LENGTH_EXCEEDED, MAX_REGEX_LENGTH);
}
state.trackOperation();
try {
Pattern pattern = this.patternCache.get(regex);
if (pattern == null) {
checkRegexLength(regex);
pattern = Pattern.compile(regex);
this.patternCache.putIfAbsent(regex, pattern);
}
Matcher matcher = pattern.matcher(new MatcherInput(input, new AccessCount()));
return BooleanTypedValue.forValue(matcher.matches());
}
@@ -141,6 +117,13 @@ public class OperatorMatches extends Operator {
}
}
private void checkRegexLength(String regex) {
if (regex.length() > MAX_REGEX_LENGTH) {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.MAX_REGEX_LENGTH_EXCEEDED, MAX_REGEX_LENGTH);
}
}
private static class AccessCount {
@@ -48,7 +48,6 @@ public class OperatorNot extends SpelNodeImpl { // Not is a unary operator so d
if (value == null) {
throw new SpelEvaluationException(SpelMessage.TYPE_CONVERSION_ERROR, "null", "boolean");
}
state.trackOperation();
return BooleanTypedValue.forValue(!value);
}
catch (SpelEvaluationException ex) {
@@ -48,7 +48,6 @@ public class OperatorPower extends Operator {
Object rightOperand = rightOp.getValueInternal(state).getValue();
if (leftOperand instanceof Number leftNumber && rightOperand instanceof Number rightNumber) {
state.trackOperation();
if (leftNumber instanceof BigDecimal) {
BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
return new TypedValue(leftBigDecimal.pow(rightNumber.intValue()));
@@ -79,13 +79,11 @@ public class Projection extends SpelNodeImpl {
// that can be referenced in the operation -- for example,
// {'a':'y', 'b':'n'}.![value == 'y' ? key : null] evaluates to ['a', null].
if (operand instanceof Map<?, ?> mapData) {
state.trackOperation();
List<Object> result = new ArrayList<>();
for (Map.Entry<?, ?> entry : mapData.entrySet()) {
try {
state.pushActiveContextObject(new TypedValue(entry));
state.enterScope();
state.trackOperation();
result.add(this.children[0].getValueInternal(state).getValue());
}
finally {
@@ -101,7 +99,6 @@ public class Projection extends SpelNodeImpl {
Iterable<?> data = (operand instanceof Iterable<?> iterable ?
iterable : Arrays.asList(ObjectUtils.toObjectArray(operand)));
state.trackOperation();
List<Object> result = new ArrayList<>();
Class<?> arrayElementType = null;
for (Object element : data) {
@@ -112,7 +109,6 @@ public class Projection extends SpelNodeImpl {
if (value != null && operandIsArray) {
arrayElementType = determineCommonType(arrayElementType, value.getClass());
}
state.trackOperation();
result.add(value);
}
finally {
@@ -91,14 +91,14 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
@Override
public ValueRef getValueRef(ExpressionState state) throws EvaluationException {
return new AccessorValueRef(this, state.getActiveContextObject(), state, state.getEvaluationContext(),
return new AccessorValueRef(this, state.getActiveContextObject(), state.getEvaluationContext(),
state.getConfiguration().isAutoGrowNullReferences());
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue tv = getValueInternal(state, state.getActiveContextObject(),
state.getEvaluationContext(), state.getConfiguration().isAutoGrowNullReferences());
TypedValue tv = getValueInternal(state.getActiveContextObject(), state.getEvaluationContext(),
state.getConfiguration().isAutoGrowNullReferences());
PropertyAccessor accessorToUse = this.cachedReadAccessor;
if (accessorToUse instanceof CompilablePropertyAccessor compilablePropertyAccessor) {
setExitTypeDescriptor(CodeFlow.toDescriptor(compilablePropertyAccessor.getPropertyType()));
@@ -106,10 +106,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
return tv;
}
private TypedValue getValueInternal(ExpressionState state, TypedValue contextObject,
EvaluationContext evalContext, boolean isAutoGrowNullReferences) throws EvaluationException {
private TypedValue getValueInternal(TypedValue contextObject, EvaluationContext evalContext,
boolean isAutoGrowNullReferences) throws EvaluationException {
TypedValue result = readProperty(state, contextObject, evalContext, this.name);
TypedValue result = readProperty(contextObject, evalContext, this.name);
// Dynamically create the objects if the user has requested that optional behavior
if (result.getValue() == null && isAutoGrowNullReferences &&
@@ -119,18 +119,16 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
// Create a new collection or map ready for the indexer
if (List.class == resultDescriptor.getType()) {
if (isWritableProperty(this.name, contextObject, evalContext)) {
state.trackOperation();
List<?> newList = new ArrayList<>();
writeProperty(state, contextObject, evalContext, this.name, newList);
result = readProperty(state, contextObject, evalContext, this.name);
writeProperty(contextObject, evalContext, this.name, newList);
result = readProperty(contextObject, evalContext, this.name);
}
}
else if (Map.class == resultDescriptor.getType()) {
if (isWritableProperty(this.name,contextObject, evalContext)) {
state.trackOperation();
Map<?,?> newMap = new HashMap<>();
writeProperty(state, contextObject, evalContext, this.name, newMap);
result = readProperty(state, contextObject, evalContext, this.name);
writeProperty(contextObject, evalContext, this.name, newMap);
result = readProperty(contextObject, evalContext, this.name);
}
}
else {
@@ -138,10 +136,9 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
try {
if (isWritableProperty(this.name,contextObject, evalContext)) {
Class<?> clazz = resultDescriptor.getType();
state.trackOperation();
Object newObject = ReflectionUtils.accessibleConstructor(clazz).newInstance();
writeProperty(state, contextObject, evalContext, this.name, newObject);
result = readProperty(state, contextObject, evalContext, this.name);
writeProperty(contextObject, evalContext, this.name, newObject);
result = readProperty(contextObject, evalContext, this.name);
}
}
catch (InvocationTargetException ex) {
@@ -162,7 +159,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
throws EvaluationException {
TypedValue typedValue = valueSupplier.get();
writeProperty(state, state.getActiveContextObject(), state.getEvaluationContext(), this.name, typedValue.getValue());
writeProperty(state.getActiveContextObject(), state.getEvaluationContext(), this.name, typedValue.getValue());
return typedValue;
}
@@ -181,8 +178,8 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
* @return the value of the property
* @throws EvaluationException if any problem accessing the property, or if it cannot be found
*/
private TypedValue readProperty(ExpressionState state, TypedValue contextObject,
EvaluationContext evalContext, String name) throws EvaluationException {
private TypedValue readProperty(TypedValue contextObject, EvaluationContext evalContext, String name)
throws EvaluationException {
Object targetObject = contextObject.getValue();
if (targetObject == null && isNullSafe()) {
@@ -193,7 +190,6 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
if (accessorToUse != null) {
if (evalContext.getPropertyAccessors().contains(accessorToUse)) {
try {
state.trackOperation();
return accessorToUse.read(evalContext, targetObject, name);
}
catch (Exception ex) {
@@ -217,15 +213,11 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
evalContext, targetObject, name);
}
this.cachedReadAccessor = accessor;
state.trackOperation();
return accessor.read(evalContext, targetObject, name);
}
}
}
catch (Exception ex) {
if (ex instanceof SpelEvaluationException see) {
throw see;
}
throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_DURING_PROPERTY_READ, name, ex.getMessage());
}
@@ -238,8 +230,9 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
}
}
private void writeProperty(ExpressionState state, TypedValue contextObject, EvaluationContext evalContext,
String name, @Nullable Object newValue) throws EvaluationException {
private void writeProperty(
TypedValue contextObject, EvaluationContext evalContext, String name, @Nullable Object newValue)
throws EvaluationException {
Object targetObject = contextObject.getValue();
if (targetObject == null) {
@@ -254,7 +247,6 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
if (accessorToUse != null) {
if (evalContext.getPropertyAccessors().contains(accessorToUse)) {
try {
state.trackOperation();
accessorToUse.write(evalContext, targetObject, name, newValue);
return;
}
@@ -272,7 +264,6 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
for (PropertyAccessor accessor : accessorsToTry) {
if (accessor.canWrite(evalContext, targetObject, name)) {
this.cachedWriteAccessor = accessor;
state.trackOperation();
accessor.write(evalContext, targetObject, name, newValue);
return;
}
@@ -366,18 +357,15 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
private final TypedValue contextObject;
private final ExpressionState expressionState;
private final EvaluationContext evalContext;
private final boolean autoGrowNullReferences;
public AccessorValueRef(PropertyOrFieldReference propertyOrFieldReference, TypedValue activeContextObject,
ExpressionState expressionState, EvaluationContext evalContext, boolean autoGrowNullReferences) {
EvaluationContext evalContext, boolean autoGrowNullReferences) {
this.ref = propertyOrFieldReference;
this.contextObject = activeContextObject;
this.expressionState = expressionState;
this.evalContext = evalContext;
this.autoGrowNullReferences = autoGrowNullReferences;
}
@@ -385,7 +373,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
@Override
public TypedValue getValue() {
TypedValue value =
this.ref.getValueInternal(this.expressionState, this.contextObject, this.evalContext, this.autoGrowNullReferences);
this.ref.getValueInternal(this.contextObject, this.evalContext, this.autoGrowNullReferences);
if (this.ref.cachedReadAccessor instanceof CompilablePropertyAccessor compilablePropertyAccessor) {
this.ref.setExitTypeDescriptor(CodeFlow.toDescriptor(compilablePropertyAccessor.getPropertyType()));
}
@@ -394,7 +382,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
@Override
public void setValue(@Nullable Object newValue) {
this.ref.writeProperty(this.expressionState, this.contextObject, this.evalContext, this.ref.name, newValue);
this.ref.writeProperty(this.contextObject, this.evalContext, this.ref.name, newValue);
}
@Override
@@ -99,7 +99,6 @@ public class Selection extends SpelNodeImpl {
SpelNodeImpl selectionCriteria = this.children[0];
if (operand instanceof Map<?, ?> mapdata) {
state.trackOperation();
Map<Object, Object> result = new HashMap<>();
Object lastKey = null;
@@ -111,7 +110,6 @@ public class Selection extends SpelNodeImpl {
Object val = selectionCriteria.getValueInternal(state).getValue();
if (val instanceof Boolean b) {
if (b) {
state.trackOperation();
result.put(entry.getKey(), entry.getValue());
if (this.variant == FIRST) {
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result), this);
@@ -148,7 +146,6 @@ public class Selection extends SpelNodeImpl {
Iterable<?> data = (operand instanceof Iterable<?> iterable ? iterable :
Arrays.asList(ObjectUtils.toObjectArray(operand)));
state.trackOperation();
List<Object> result = new ArrayList<>();
for (Object element : data) {
try {
@@ -160,7 +157,6 @@ public class Selection extends SpelNodeImpl {
if (this.variant == FIRST) {
return new ValueRef.TypedValueHolderValueRef(new TypedValue(element), this);
}
state.trackOperation();
result.add(element);
}
}

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