From e72b523995db89d7de4f3f4a31b1ac9e1f2d0f93 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:46:44 +0100 Subject: [PATCH] Polish SpEL support --- .../spel/CompilablePropertyAccessor.java | 15 ++++----- .../support/DataBindingMethodResolver.java | 4 +-- .../support/DataBindingPropertyAccessor.java | 4 +-- .../spel/support/ReflectionHelper.java | 12 +++---- .../ReflectiveConstructorResolver.java | 18 ++++++----- .../support/ReflectiveMethodExecutor.java | 17 +++++----- .../support/ReflectiveMethodResolver.java | 17 +++++----- .../support/ReflectivePropertyAccessor.java | 13 +------- .../spel/support/SimpleEvaluationContext.java | 10 +++--- .../spel/support/StandardComponentsTests.java | 31 ++++++++----------- 10 files changed, 66 insertions(+), 75 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/CompilablePropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/CompilablePropertyAccessor.java index 634267d75bc2..6651bd688878 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/CompilablePropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/CompilablePropertyAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 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. @@ -21,7 +21,7 @@ import org.springframework.expression.PropertyAccessor; /** - * A compilable property accessor is able to generate bytecode that represents + * A compilable {@link PropertyAccessor} is able to generate bytecode that represents * the access operation, facilitating compilation to bytecode of expressions * that use the accessor. * @@ -41,12 +41,13 @@ public interface CompilablePropertyAccessor extends PropertyAccessor, Opcodes { Class getPropertyType(); /** - * Generate the bytecode the performs the access operation into the specified MethodVisitor - * using context information from the codeflow where necessary. + * Generate the bytecode the performs the access operation into the specified + * {@link MethodVisitor} using context information from the {@link CodeFlow} + * where necessary. * @param propertyName the name of the property - * @param mv the Asm method visitor into which code should be generated - * @param cf the current state of the expression compiler + * @param methodVisitor the ASM method visitor into which code should be generated + * @param codeFlow the current state of the expression compiler */ - void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf); + void generateCode(String propertyName, MethodVisitor methodVisitor, CodeFlow codeFlow); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/DataBindingMethodResolver.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/DataBindingMethodResolver.java index 36dbeb10e7b4..120bb0806173 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/DataBindingMethodResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/DataBindingMethodResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 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. @@ -27,7 +27,7 @@ import org.springframework.lang.Nullable; /** - * A {@link org.springframework.expression.MethodResolver} variant for data binding + * An {@link org.springframework.expression.MethodResolver} variant for data binding * purposes, using reflection to access instance methods on a given target object. * *

This accessor does not resolve static methods and also no technical methods diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/DataBindingPropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/DataBindingPropertyAccessor.java index 6dbe01b6adec..187fb16e7f5b 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/DataBindingPropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/DataBindingPropertyAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 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. @@ -19,7 +19,7 @@ import java.lang.reflect.Method; /** - * A {@link org.springframework.expression.PropertyAccessor} variant for data binding + * An {@link org.springframework.expression.PropertyAccessor} variant for data binding * purposes, using reflection to access properties for reading and possibly writing. * *

A property can be referenced through a public getter method (when being read) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java index c35641f2daa6..24b8934ff55f 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 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. @@ -503,14 +503,10 @@ enum ArgumentsMatchKind { * and the set that are being supplied at the point of invocation. If the kind * indicates that conversion is required for some of the arguments then the arguments * that require conversion are listed in the argsRequiringConversion array. + * + * @param kind the kind of match that was achieved */ - static class ArgumentsMatchInfo { - - private final ArgumentsMatchKind kind; - - ArgumentsMatchInfo(ArgumentsMatchKind kind) { - this.kind = kind; - } + record ArgumentsMatchInfo(ArgumentsMatchKind kind) { public boolean isExactMatch() { return (this.kind == ArgumentsMatchKind.EXACT); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java index 8ec11fabecb7..ba55a8ae83ef 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 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. @@ -33,7 +33,8 @@ import org.springframework.lang.Nullable; /** - * A constructor resolver that uses reflection to locate the constructor that should be invoked. + * A constructor resolver that uses reflection to locate the constructor that + * should be invoked. * * @author Andy Clement * @author Juergen Hoeller @@ -42,12 +43,15 @@ public class ReflectiveConstructorResolver implements ConstructorResolver { /** - * Locate a constructor on the type. There are three kinds of match that might occur: + * Locate a constructor on the type. + *

There are three kinds of matches that might occur: *

    - *
  1. An exact match where the types of the arguments match the types of the constructor - *
  2. An in-exact match where the types we are looking for are subtypes of those defined on the constructor - *
  3. A match where we are able to convert the arguments into those expected by the constructor, according to the - * registered type converter. + *
  4. An exact match where the types of the arguments match the types of the + * constructor.
  5. + *
  6. An inexact match where the types we are looking for are subtypes of + * those defined on the constructor.
  7. + *
  8. A match where we are able to convert the arguments into those expected + * by the constructor, according to the registered type converter.
  9. *
*/ @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java index 5e4b50187f8c..9de4917c0d57 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 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. @@ -87,12 +87,15 @@ public final Method getMethod() { } /** - * Find the first public class in the methods declaring class hierarchy that declares this method. - * Sometimes the reflective method discovery logic finds a suitable method that can easily be - * called via reflection but cannot be called from generated code when compiling the expression - * because of visibility restrictions. For example if a non-public class overrides toString(), - * this helper method will walk up the type hierarchy to find the first public type that declares - * the method (if there is one!). For toString() it may walk as far as Object. + * Find the first public class in the method's declaring class hierarchy that + * declares this method. + *

Sometimes the reflective method discovery logic finds a suitable method + * that can easily be called via reflection but cannot be called from generated + * code when compiling the expression because of visibility restrictions. For + * example, if a non-public class overrides {@code toString()}, this helper + * method will traverse up the type hierarchy to find the first public type that + * declares the method (if there is one). For {@code toString()}, it may traverse + * as far as Object. */ @Nullable public Class getPublicDeclaringClass() { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java index c4ce19aa3d78..dbf696c13a2e 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 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. @@ -63,7 +63,7 @@ public class ReflectiveMethodResolver implements MethodResolver { public ReflectiveMethodResolver() { - this.useDistance = true; + this(true); } /** @@ -100,12 +100,15 @@ public void registerMethodFilter(Class type, @Nullable MethodFilter filter) { } /** - * Locate a method on a type. There are three kinds of match that might occur: + * Locate a method on the type. + *

There are three kinds of matches that might occur: *

    - *
  1. an exact match where the types of the arguments match the types of the constructor - *
  2. an in-exact match where the types we are looking for are subtypes of those defined on the constructor - *
  3. a match where we are able to convert the arguments into those expected by the constructor, - * according to the registered type converter + *
  4. An exact match where the types of the arguments match the types of the + * method.
  5. + *
  6. An inexact match where the types we are looking for are subtypes of + * those defined on the method.
  7. + *
  8. A match where we are able to convert the arguments into those expected + * by the method, according to the registered type converter.
  9. *
*/ @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java index a05ee3679b58..632860a4a377 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java @@ -579,18 +579,7 @@ private static boolean isKotlinProperty(Method method, String methodSuffix) { * Captures the member (method/field) to call reflectively to access a property value * and the type descriptor for the value returned by the reflective call. */ - private static class InvokerPair { - - final Member member; - - final TypeDescriptor typeDescriptor; - - public InvokerPair(Member member, TypeDescriptor typeDescriptor) { - this.member = member; - this.typeDescriptor = typeDescriptor; - } - } - + private record InvokerPair(Member member, TypeDescriptor typeDescriptor) {} private static final class PropertyCacheKey implements Comparable { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java index 1168c9c91a26..7d65bba9003e 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 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. @@ -103,9 +103,9 @@ public final class SimpleEvaluationContext implements EvaluationContext { private final TypeConverter typeConverter; - private final TypeComparator typeComparator = new StandardTypeComparator(); + private final TypeComparator typeComparator = StandardTypeComparator.INSTANCE; - private final OperatorOverloader operatorOverloader = new StandardOperatorOverloader(); + private final OperatorOverloader operatorOverloader = StandardOperatorOverloader.INSTANCE; private final Map variables = new HashMap<>(); @@ -168,7 +168,7 @@ public BeanResolver getBeanResolver() { /** * {@code SimpleEvaluationContext} does not support use of type references. * @return {@code TypeLocator} implementation that raises a - * {@link SpelEvaluationException} with {@link SpelMessage#TYPE_NOT_FOUND}. + * {@link SpelEvaluationException} with {@link SpelMessage#TYPE_NOT_FOUND} */ @Override public TypeLocator getTypeLocator() { @@ -315,7 +315,6 @@ public Builder withInstanceMethods() { return this; } - /** * Register a custom {@link ConversionService}. *

By default a {@link StandardTypeConverter} backed by a @@ -327,6 +326,7 @@ public Builder withConversionService(ConversionService conversionService) { this.typeConverter = new StandardTypeConverter(conversionService); return this; } + /** * Register a custom {@link TypeConverter}. *

By default a {@link StandardTypeConverter} backed by a diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/support/StandardComponentsTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/support/StandardComponentsTests.java index 9fa206165a1c..aff6b030706c 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/support/StandardComponentsTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/support/StandardComponentsTests.java @@ -16,8 +16,6 @@ package org.springframework.expression.spel.support; -import java.util.List; - import org.junit.jupiter.api.Test; import org.springframework.core.convert.TypeDescriptor; @@ -31,14 +29,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -public class StandardComponentsTests { +class StandardComponentsTests { @Test - void testStandardEvaluationContext() { + void standardEvaluationContext() { StandardEvaluationContext context = new StandardEvaluationContext(); assertThat(context.getTypeComparator()).isNotNull(); - TypeComparator tc = new StandardTypeComparator(); + TypeComparator tc = StandardTypeComparator.INSTANCE; context.setTypeComparator(tc); assertThat(context.getTypeComparator()).isEqualTo(tc); @@ -48,28 +46,25 @@ void testStandardEvaluationContext() { } @Test - void testStandardOperatorOverloader() throws EvaluationException { - OperatorOverloader oo = new StandardOperatorOverloader(); - assertThat(oo.overridesOperation(Operation.ADD, null, null)).isFalse(); - assertThatExceptionOfType(EvaluationException.class).isThrownBy(() -> - oo.operate(Operation.ADD, 2, 3)); + void standardOperatorOverloader() { + OperatorOverloader overloader = new StandardOperatorOverloader(); + assertThat(overloader.overridesOperation(Operation.ADD, null, null)).isFalse(); + assertThatExceptionOfType(EvaluationException.class) + .isThrownBy(() -> overloader.operate(Operation.ADD, 2, 3)); } @Test - void testStandardTypeLocator() { + void standardTypeLocator() { StandardTypeLocator tl = new StandardTypeLocator(); - List prefixes = tl.getImportPrefixes(); - assertThat(prefixes).hasSize(1); + assertThat(tl.getImportPrefixes()).hasSize(1); tl.registerImport("java.util"); - prefixes = tl.getImportPrefixes(); - assertThat(prefixes).hasSize(2); + assertThat(tl.getImportPrefixes()).hasSize(2); tl.removeImport("java.util"); - prefixes = tl.getImportPrefixes(); - assertThat(prefixes).hasSize(1); + assertThat(tl.getImportPrefixes()).hasSize(1); } @Test - void testStandardTypeConverter() throws EvaluationException { + void standardTypeConverter() { TypeConverter tc = new StandardTypeConverter(); tc.convertValue(3, TypeDescriptor.forObject(3), TypeDescriptor.valueOf(Double.class)); }