Skip to content

Introduce ReflectiveIndexAccessor convenience class in SpEL #32714

@sbrannen

Description

@sbrannen

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

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions