From 4b0a04857004e3d0e1040a1e70d6ac78af086a6f Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:01:26 +0100 Subject: [PATCH] Polish SpEL internals and remove duplicate code --- .../expression/ConstructorExecutor.java | 2 +- .../expression/PropertyAccessor.java | 2 +- .../expression/spel/ast/AstUtils.java | 62 ++++++++++--------- .../spel/ast/PropertyOrFieldReference.java | 39 ++---------- 4 files changed, 40 insertions(+), 65 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java b/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java index 0edeedd8e2f..363506b951f 100644 --- a/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java +++ b/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java @@ -34,7 +34,7 @@ package org.springframework.expression; * @author Andy Clement * @author Sam Brannen * @since 3.0 - * @see MethodResolver + * @see ConstructorResolver * @see MethodExecutor */ @FunctionalInterface diff --git a/spring-expression/src/main/java/org/springframework/expression/PropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/PropertyAccessor.java index a7d34d9292d..c063750b1b1 100644 --- a/spring-expression/src/main/java/org/springframework/expression/PropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/PropertyAccessor.java @@ -45,7 +45,7 @@ public interface PropertyAccessor { /** * Return an array of classes for which this property accessor should be called. *

Returning {@code null} indicates this is a general property accessor that - * can be called in an attempt to resolve a property on any type. + * can be called in an attempt to access a property on any type. * @return an array of classes that this property accessor is suitable for * (or {@code null} if a general property accessor) */ diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java index ac7a9f0db16..e4e710f7d61 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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,9 +21,10 @@ import java.util.List; import org.springframework.expression.PropertyAccessor; import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; /** - * Utilities methods for use in the Ast classes. + * Utility methods for use in the AST classes. * * @author Andy Clement * @since 3.0.2 @@ -31,45 +32,48 @@ import org.springframework.lang.Nullable; public abstract class AstUtils { /** - * Determines the set of property resolvers that should be used to try and access a - * property on the specified target type. The resolvers are considered to be in an - * ordered list, however in the returned list any that are exact matches for the input - * target type (as opposed to 'general' resolvers that could work for any type) are - * placed at the start of the list. In addition, there are specific resolvers that - * exactly name the class in question and resolvers that name a specific class but it - * is a supertype of the class we have. These are put at the end of the specific resolvers - * set and will be tried after exactly matching accessors but before generic accessors. + * Determine the set of property accessors that should be used to try to + * access a property on the specified target type. + *

The accessors are considered to be in an ordered list; however, in the + * returned list any accessors that are exact matches for the input target + * type (as opposed to 'general' accessors that could work for any type) are + * placed at the start of the list. In addition, if there are specific + * accessors that exactly name the class in question and accessors that name + * a specific class which is a supertype of the class in question, the latter + * are put at the end of the specific accessors set and will be tried after + * exactly matching accessors but before generic accessors. * @param targetType the type upon which property access is being attempted - * @return a list of resolvers that should be tried in order to access the property + * @param propertyAccessors the list of property accessors to process + * @return a list of accessors that should be tried in order to access the property */ public static List getPropertyAccessorsToTry( @Nullable Class targetType, List propertyAccessors) { List specificAccessors = new ArrayList<>(); List generalAccessors = new ArrayList<>(); - for (PropertyAccessor resolver : propertyAccessors) { - Class[] targets = resolver.getSpecificTargetClasses(); - if (targets == null) { // generic resolver that says it can be used for any type - generalAccessors.add(resolver); + for (PropertyAccessor accessor : propertyAccessors) { + Class[] targets = accessor.getSpecificTargetClasses(); + if (ObjectUtils.isEmpty(targets)) { + // generic accessor that says it can be used for any type + generalAccessors.add(accessor); } - else { - if (targetType != null) { - for (Class clazz : targets) { - if (clazz == targetType) { // put exact matches on the front to be tried first? - specificAccessors.add(resolver); - } - else if (clazz.isAssignableFrom(targetType)) { // put supertype matches at the end of the - // specificAccessor list - generalAccessors.add(resolver); - } + else if (targetType != null) { + for (Class clazz : targets) { + if (clazz == targetType) { + // add exact matches to the specificAccessors list + specificAccessors.add(accessor); + } + else if (clazz.isAssignableFrom(targetType)) { + // add supertype matches to the front of the generalAccessors list + generalAccessors.add(0, accessor); } } } } - List resolvers = new ArrayList<>(specificAccessors.size() + generalAccessors.size()); - resolvers.addAll(specificAccessors); - resolvers.addAll(generalAccessors); - return resolvers; + List accessors = new ArrayList<>(specificAccessors.size() + generalAccessors.size()); + accessors.addAll(specificAccessors); + accessors.addAll(generalAccessors); + return accessors; } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java index 84acd5ef05a..35a08b3f528 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java @@ -298,46 +298,17 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } /** - * Determines the set of property resolvers that should be used to try and access a property - * on the specified target type. The resolvers are considered to be in an ordered list, - * however in the returned list any that are exact matches for the input target type (as - * opposed to 'general' resolvers that could work for any type) are placed at the start of the - * list. In addition, there are specific resolvers that exactly name the class in question - * and resolvers that name a specific class but it is a supertype of the class we have. - * These are put at the end of the specific resolvers set and will be tried after exactly - * matching accessors but before generic accessors. + * Determine the set of property accessors that should be used to try to + * access a property on the specified context object. + *

Delegates to {@link AstUtils#getPropertyAccessorsToTry(Class, List)}. * @param contextObject the object upon which property access is being attempted - * @return a list of resolvers that should be tried in order to access the property + * @return a list of accessors that should be tried in order to access the property */ private List getPropertyAccessorsToTry( @Nullable Object contextObject, List propertyAccessors) { Class targetType = (contextObject != null ? contextObject.getClass() : null); - - List specificAccessors = new ArrayList<>(); - List generalAccessors = new ArrayList<>(); - for (PropertyAccessor resolver : propertyAccessors) { - Class[] targets = resolver.getSpecificTargetClasses(); - if (targets == null) { - // generic resolver that says it can be used for any type - generalAccessors.add(resolver); - } - else if (targetType != null) { - for (Class clazz : targets) { - if (clazz == targetType) { - specificAccessors.add(resolver); - break; - } - else if (clazz.isAssignableFrom(targetType)) { - generalAccessors.add(resolver); - } - } - } - } - List resolvers = new ArrayList<>(specificAccessors); - generalAccessors.removeAll(specificAccessors); - resolvers.addAll(generalAccessors); - return resolvers; + return AstUtils.getPropertyAccessorsToTry(targetType, propertyAccessors); } @Override