Browse Source

Support String index type in custom IndexAccessor

Closes gh-32706
pull/32703/head
Sam Brannen 2 years ago
parent
commit
14689256c4
  1. 17
      spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java
  2. 66
      spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java

17
spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java

@ -244,14 +244,7 @@ public class Indexer extends SpelNodeImpl { @@ -244,14 +244,7 @@ public class Indexer extends SpelNodeImpl {
}
}
// Try to treat the index value as a property of the context object.
TypeDescriptor valueType = indexValue.getTypeDescriptor();
if (valueType != null && String.class == valueType.getType()) {
this.indexedType = IndexedType.OBJECT;
return new PropertyAccessorValueRef(
target, (String) index, state.getEvaluationContext(), targetDescriptor);
}
// Check for a custom IndexAccessor.
EvaluationContext evalContext = state.getEvaluationContext();
List<IndexAccessor> accessorsToTry = getIndexAccessorsToTry(target, evalContext.getIndexAccessors());
if (accessMode.supportsReads) {
@ -285,6 +278,14 @@ public class Indexer extends SpelNodeImpl { @@ -285,6 +278,14 @@ public class Indexer extends SpelNodeImpl {
}
}
// As a last resort, try to treat the index value as a property of the context object.
TypeDescriptor valueType = indexValue.getTypeDescriptor();
if (valueType != null && String.class == valueType.getType()) {
this.indexedType = IndexedType.OBJECT;
return new PropertyAccessorValueRef(
target, (String) index, state.getEvaluationContext(), targetDescriptor);
}
throw new SpelEvaluationException(
getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetDescriptor);
}

66
spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java

@ -711,6 +711,72 @@ class IndexingTests { @@ -711,6 +711,72 @@ class IndexingTests {
assertThat(expr.getValue(context, arrayNode)).isSameAs(node1);
}
@Test // gh-32706
void readIndexWithStringIndexType() {
BirdNameToColorMappings birdNameMappings = new BirdNameToColorMappings();
// Without a registered BirdNameToColorMappingsIndexAccessor, we should
// be able to index into an object via a property name.
Expression propertyExpression = parser.parseExpression("['property']");
assertThat(propertyExpression.getValue(context, birdNameMappings)).isEqualTo("enigma");
context.addIndexAccessor(new BirdNameToColorMappingsIndexAccessor());
Expression expression = parser.parseExpression("['cardinal']");
assertThat(expression.getValue(context, birdNameMappings)).isEqualTo(Color.RED);
// With a registered BirdNameToColorMappingsIndexAccessor, an attempt
// to index into an object via a property name should fail.
assertThatExceptionOfType(SpelEvaluationException.class)
.isThrownBy(() -> propertyExpression.getValue(context, birdNameMappings))
.withMessageEndingWith("A problem occurred while attempting to read index '%s' in '%s'",
"property", BirdNameToColorMappings.class.getName())
.havingCause().withMessage("unknown bird color: property");
}
static class BirdNameToColorMappings {
public final String property = "enigma";
public Color get(String name) {
return switch (name) {
case "cardinal" -> Color.RED;
case "blue jay" -> Color.BLUE;
default -> throw new RuntimeException("unknown bird color: " + name);
};
}
}
static class BirdNameToColorMappingsIndexAccessor implements IndexAccessor {
@Override
public Class<?>[] getSpecificTargetClasses() {
return new Class[] { BirdNameToColorMappings.class };
}
@Override
public boolean canRead(EvaluationContext context, Object target, Object index) {
return (target instanceof BirdNameToColorMappings && index instanceof String);
}
@Override
public TypedValue read(EvaluationContext context, Object target, Object index) {
BirdNameToColorMappings mappings = (BirdNameToColorMappings) target;
String name = (String) index;
return new TypedValue(mappings.get(name));
}
@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();
}
}
/**
* {@link IndexAccessor} that knows how to read and write indexes in a
* Jackson {@link ArrayNode}.

Loading…
Cancel
Save