Polish SpEL support

This commit is contained in:
Sam Brannen
2024-02-09 13:46:44 +01:00
parent 99bdc4211d
commit e72b523995
10 changed files with 66 additions and 75 deletions

View File

@@ -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.asm.Opcodes;
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);
}

View File

@@ -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.expression.MethodExecutor;
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.
*
* <p>This accessor does not resolve static methods and also no technical methods

View File

@@ -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 @@ package org.springframework.expression.spel.support;
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.
*
* <p>A property can be referenced through a public getter method (when being read)

View File

@@ -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 @@ public abstract class ReflectionHelper {
* 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);

View File

@@ -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.expression.TypeConverter;
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 @@ import org.springframework.lang.Nullable;
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.
* <p>There are three kinds of matches that might occur:
* <ol>
* <li>An exact match where the types of the arguments match the types of the constructor
* <li>An in-exact match where the types we are looking for are subtypes of those defined on the constructor
* <li>A match where we are able to convert the arguments into those expected by the constructor, according to the
* registered type converter.
* <li>An exact match where the types of the arguments match the types of the
* constructor.</li>
* <li>An inexact match where the types we are looking for are subtypes of
* those defined on the constructor.</li>
* <li>A match where we are able to convert the arguments into those expected
* by the constructor, according to the registered type converter.</li>
* </ol>
*/
@Override

View File

@@ -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 class ReflectiveMethodExecutor implements MethodExecutor {
}
/**
* 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.
* <p>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() {

View File

@@ -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 class ReflectiveMethodResolver implements MethodResolver {
}
/**
* Locate a method on a type. There are three kinds of match that might occur:
* Locate a method on the type.
* <p>There are three kinds of matches that might occur:
* <ol>
* <li>an exact match where the types of the arguments match the types of the constructor
* <li>an in-exact match where the types we are looking for are subtypes of those defined on the constructor
* <li>a match where we are able to convert the arguments into those expected by the constructor,
* according to the registered type converter
* <li>An exact match where the types of the arguments match the types of the
* method.</li>
* <li>An inexact match where the types we are looking for are subtypes of
* those defined on the method.</li>
* <li>A match where we are able to convert the arguments into those expected
* by the method, according to the registered type converter.</li>
* </ol>
*/
@Override

View File

@@ -579,18 +579,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
* 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<PropertyCacheKey> {

View File

@@ -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<String, Object> variables = new HashMap<>();
@@ -168,7 +168,7 @@ public final class SimpleEvaluationContext implements EvaluationContext {
/**
* {@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 final class SimpleEvaluationContext implements EvaluationContext {
return this;
}
/**
* Register a custom {@link ConversionService}.
* <p>By default a {@link StandardTypeConverter} backed by a
@@ -327,6 +326,7 @@ public final class SimpleEvaluationContext implements EvaluationContext {
this.typeConverter = new StandardTypeConverter(conversionService);
return this;
}
/**
* Register a custom {@link TypeConverter}.
* <p>By default a {@link StandardTypeConverter} backed by a

View File

@@ -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 org.springframework.expression.TypeLocator;
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 @@ public class StandardComponentsTests {
}
@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<String> 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));
}