Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce ReflectiveIndexAccessor convenience class in SpEL #32714

Closed
sbrannen opened this issue Apr 26, 2024 · 2 comments
Closed

Introduce ReflectiveIndexAccessor convenience class in SpEL #32714

sbrannen opened this issue Apr 26, 2024 · 2 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Milestone

Comments

@sbrannen
Copy link
Member

Overview

Somewhat analogous to the ReflectivePropertyAccessor implementation of PropertyAccessor, we should introduce a general purpose ReflectiveIndexAccessor implementation as a convenience for users.

However, ReflectiveIndexAccessor should implement CompilableIndexAccessor (instead of just IndexAccessor) in order to provide built-in compilation support.

A proof of concept has already been implemented in the tests for CompilableIndexAccessor:

/**
* {@link CompilableIndexAccessor} that uses reflection to invoke the
* configured read-method for index access operations.
*/
static class ReflectiveIndexAccessor implements CompilableIndexAccessor {
private final Class<?> targetType;
private final Class<?> indexType;
private final Method readMethod;
private final Method readMethodToInvoke;
private final String targetTypeDesc;
private final String methodDescr;
public ReflectiveIndexAccessor(Class<?> targetType, Class<?> indexType, String readMethodName) {
this.targetType = targetType;
this.indexType = indexType;
this.readMethod = ReflectionUtils.findMethod(targetType, readMethodName, indexType);
Assert.notNull(this.readMethod, () -> "Failed to find method '%s(%s)' in class '%s'."
.formatted(readMethodName, indexType.getTypeName(), targetType.getTypeName()));
this.readMethodToInvoke = ClassUtils.getInterfaceMethodIfPossible(this.readMethod, targetType);
this.targetTypeDesc = CodeFlow.toDescriptor(targetType);
this.methodDescr = CodeFlow.createSignatureDescriptor(this.readMethod);
}
@Override
public Class<?>[] getSpecificTargetClasses() {
return new Class[] { this.targetType };
}
@Override
public boolean canRead(EvaluationContext context, Object target, Object index) {
return (ClassUtils.isAssignableValue(this.targetType, target) &&
ClassUtils.isAssignableValue(this.indexType, index));
}
@Override
public TypedValue read(EvaluationContext context, Object target, Object index) {
ReflectionUtils.makeAccessible(this.readMethodToInvoke);
Object value = ReflectionUtils.invokeMethod(this.readMethodToInvoke, target, index);
return new TypedValue(value);
}
@Override
public boolean canWrite(EvaluationContext context, Object target, Object index) {
return false;
}
@Override
public void write(EvaluationContext context, Object target, Object index, @Nullable Object newValue) {
throw new UnsupportedOperationException();
}
@Override
public boolean isCompilable() {
return true;
}
@Override
public Class<?> getIndexedValueType() {
return this.readMethod.getReturnType();
}
@Override
public void generateCode(SpelNode index, MethodVisitor mv, CodeFlow cf) {
// Determine the public declaring class.
Class<?> publicDeclaringClass = this.readMethodToInvoke.getDeclaringClass();
if (!Modifier.isPublic(publicDeclaringClass.getModifiers()) && this.readMethod != null) {
publicDeclaringClass = CodeFlow.findPublicDeclaringClass(this.readMethod);
}
Assert.state(publicDeclaringClass != null && Modifier.isPublic(publicDeclaringClass.getModifiers()),
() -> "Failed to find public declaring class for: " + this.readMethod);
// Ensure the current object on the stack is the target type.
String lastDesc = cf.lastDescriptor();
if (lastDesc == null || !lastDesc.equals(this.targetTypeDesc)) {
CodeFlow.insertCheckCast(mv, this.targetTypeDesc);
}
// Push the index onto the stack.
cf.generateCodeForArgument(mv, index, this.indexType);
// Invoke the read method.
String classDesc = publicDeclaringClass.getName().replace('.', '/');
boolean isStatic = Modifier.isStatic(this.readMethod.getModifiers());
boolean isInterface = publicDeclaringClass.isInterface();
int opcode = (isStatic ? INVOKESTATIC : isInterface ? INVOKEINTERFACE : INVOKEVIRTUAL);
mv.visitMethodInsn(opcode, classDesc, this.readMethod.getName(), this.methodDescr, isInterface);
}
}

Related Issues

@sbrannen sbrannen added in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement labels Apr 26, 2024
@sbrannen sbrannen added this to the 6.2.0-M2 milestone Apr 26, 2024
@sbrannen sbrannen self-assigned this Apr 26, 2024
sbrannen added a commit to sbrannen/spring-framework that referenced this issue Apr 28, 2024
@sbrannen
Copy link
Member Author

Current work on this can be viewed in the following feature branch.

main...sbrannen:spring-framework:issues/gh-32714-ReflectiveIndexAccessor

sbrannen added a commit to sbrannen/spring-framework that referenced this issue Apr 28, 2024
sbrannen added a commit to sbrannen/spring-framework that referenced this issue Apr 29, 2024
sbrannen added a commit to sbrannen/spring-framework that referenced this issue Apr 29, 2024
sbrannen added a commit to sbrannen/spring-framework that referenced this issue Apr 30, 2024
sbrannen added a commit to sbrannen/spring-framework that referenced this issue Apr 30, 2024
This commit introduces ReflectiveIndexAccessor for the Spring
Expression Language (SpEL) which is somewhat analogous to the
ReflectivePropertyAccessor implementation of PropertyAccessor.

ReflectiveIndexAccessor is a flexible IndexAccessor implementation that
uses reflection to read from and optionally write to an indexed
structure of a target object. ReflectiveIndexAccessor also implements
CompilableIndexAccessor in order to support compilation to bytecode for
read access.

Closes spring-projectsgh-32714
@sbrannen
Copy link
Member Author

@artembilan, @pilak, @jackmiking, @jdomigon, @martin-jamszolik: you may also be interested in the new ReflectiveIndexAccessor that I just pushed to main for inclusion in Spring Framework 6.2 M2.

It greatly simplifies custom index access with zero code (plus built-in compilation support) for the most common use cases.

See commit 35c183d for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

1 participant