Skip to content

Commit 1c937a6

Browse files
committed
Change IndexAccessor#read()'s return type to TypedValue
Prior to this commit, the read() method in the IndexAccessor SPI declared a return type of ValueRef which introduced a package cycle. This commit addresses this by aligning with the PropertyAccess SPI and returning TypedValue from IndexAccessor's read() method. This commit also reworks the internals of Indexer based on a new, local IndexAccessorValueRef implementation. See spring-projectsgh-26409 See spring-projectsgh-26478
1 parent 92ecaf1 commit 1c937a6

File tree

3 files changed

+68
-39
lines changed

3 files changed

+68
-39
lines changed

Diff for: spring-expression/src/main/java/org/springframework/expression/IndexAccessor.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.expression;
1818

19-
import org.springframework.expression.spel.ast.ValueRef;
2019
import org.springframework.lang.Nullable;
2120

2221
/**
@@ -80,8 +79,7 @@ public interface IndexAccessor extends TargetedAccessor {
8079
* descriptor for it
8180
* @throws AccessException if there is any problem reading the index value
8281
*/
83-
// TODO Change return type to TypedValue to avoid package cycle.
84-
ValueRef read(EvaluationContext context, Object target, Object index) throws AccessException;
82+
TypedValue read(EvaluationContext context, Object target, Object index) throws AccessException;
8583

8684
/**
8785
* Called to determine if this index accessor is able to write to a specified

Diff for: spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java

+58-5
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,14 @@ public TypedValue setValueInternal(ExpressionState state, Supplier<TypedValue> v
158158
throws EvaluationException {
159159

160160
TypedValue typedValue = valueSupplier.get();
161-
// TODO Set value for IndexAccessor via its write() method, NOT via the ValueRef returned from its read() method.
161+
// TODO Query IndexAccessor's canWrite() method before invoking its write() method.
162162
getValueRef(state).setValue(typedValue.getValue());
163163
return typedValue;
164164
}
165165

166166
@Override
167167
public boolean isWritable(ExpressionState expressionState) throws SpelEvaluationException {
168-
return true;
168+
return getValueRef(expressionState).isWritable();
169169
}
170170

171171

@@ -254,13 +254,13 @@ else if (target instanceof Collection<?> collection) {
254254
try {
255255
for (IndexAccessor indexAccessor : accessorsToTry) {
256256
if (indexAccessor.canRead(evalContext, target, index)) {
257-
// TODO Introduce local IndexAccessorValueRef.
258-
return indexAccessor.read(evalContext, target, index);
257+
return new IndexAccessorValueRef(indexAccessor, target, index, evalContext, targetDescriptor);
259258
}
260259
}
261260
}
262261
catch (Exception ex) {
263-
// TODO throw SpelEvaluationException for "exception during index access"
262+
// TODO throw SpelEvaluationException for "exception during index access",
263+
// analogous to SpelMessage.EXCEPTION_DURING_PROPERTY_READ.
264264
}
265265

266266
throw new SpelEvaluationException(
@@ -874,4 +874,57 @@ public boolean isWritable() {
874874
}
875875
}
876876

877+
878+
private class IndexAccessorValueRef implements ValueRef {
879+
880+
private final IndexAccessor indexAccessor;
881+
882+
private final Object target;
883+
884+
private final Object index;
885+
886+
private final EvaluationContext evaluationContext;
887+
888+
private final TypeDescriptor typeDescriptor;
889+
890+
891+
IndexAccessorValueRef(IndexAccessor indexAccessor, Object target, Object index,
892+
EvaluationContext evaluationContext, TypeDescriptor typeDescriptor) {
893+
894+
this.indexAccessor = indexAccessor;
895+
this.target = target;
896+
this.index = index;
897+
this.evaluationContext = evaluationContext;
898+
this.typeDescriptor = typeDescriptor;
899+
}
900+
901+
902+
@Override
903+
public TypedValue getValue() {
904+
try {
905+
return this.indexAccessor.read(this.evaluationContext, this.target, this.index);
906+
}
907+
catch (AccessException ex) {
908+
throw new SpelEvaluationException(getStartPosition(), ex,
909+
SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, this.typeDescriptor.toString());
910+
}
911+
}
912+
913+
@Override
914+
public void setValue(@Nullable Object newValue) {
915+
try {
916+
this.indexAccessor.write(this.evaluationContext, this.target, this.index, newValue);
917+
}
918+
catch (AccessException ex) {
919+
throw new SpelEvaluationException(getStartPosition(), ex,
920+
SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, this.typeDescriptor.toString());
921+
}
922+
}
923+
924+
@Override
925+
public boolean isWritable() {
926+
return true;
927+
}
928+
}
929+
877930
}

Diff for: spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java

+9-31
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@
4141
import org.springframework.expression.IndexAccessor;
4242
import org.springframework.expression.PropertyAccessor;
4343
import org.springframework.expression.TypedValue;
44-
import org.springframework.expression.spel.ast.ValueRef;
4544
import org.springframework.expression.spel.standard.SpelExpressionParser;
4645
import org.springframework.expression.spel.support.StandardEvaluationContext;
4746
import org.springframework.expression.spel.testresources.Person;
47+
import org.springframework.lang.Nullable;
4848

4949
import static org.assertj.core.api.Assertions.assertThat;
5050
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -543,45 +543,23 @@ public boolean canRead(EvaluationContext context, Object target, Object index) {
543543
}
544544

545545
@Override
546-
public ValueRef read(EvaluationContext context, Object target, Object index) {
547-
return new ArrayNodeValueRef((ArrayNode) target, (Integer) index, this.objectMapper);
546+
public TypedValue read(EvaluationContext context, Object target, Object index) {
547+
ArrayNode arrayNode = (ArrayNode) target;
548+
Integer intIndex = (Integer) index;
549+
return new TypedValue(arrayNode.get(intIndex));
548550
}
549551

550552
@Override
551553
public boolean canWrite(EvaluationContext context, Object target, Object index) {
552-
return (target instanceof ArrayNode && index instanceof Integer);
554+
return canRead(context, target, index);
553555
}
554556

555557
@Override
556-
public void write(EvaluationContext context, Object target, Object index, Object newValue) {
557-
if (!(target instanceof ArrayNode arrayNode)) {
558-
throw new IllegalStateException("target must be an ArrayNode: " + target.getClass().getName());
559-
}
560-
if (!(index instanceof Integer intIndex)) {
561-
throw new IllegalStateException("index must be an integer: " + target.getClass().getName());
562-
}
563-
558+
public void write(EvaluationContext context, Object target, Object index, @Nullable Object newValue) {
559+
ArrayNode arrayNode = (ArrayNode) target;
560+
Integer intIndex = (Integer) index;
564561
arrayNode.set(intIndex, this.objectMapper.convertValue(newValue, JsonNode.class));
565562
}
566-
567-
private record ArrayNodeValueRef(ArrayNode arrayNode, int index, ObjectMapper objectMapper) implements ValueRef {
568-
569-
@Override
570-
public TypedValue getValue() {
571-
return new TypedValue(this.arrayNode.get(this.index));
572-
}
573-
574-
@Override
575-
public void setValue(Object newValue) {
576-
// TODO throw new UnsupportedOperationException("setValue() is not supported");
577-
this.arrayNode.set(index, this.objectMapper.convertValue(newValue, JsonNode.class));
578-
}
579-
580-
@Override
581-
public boolean isWritable() {
582-
return true;
583-
}
584-
}
585563
}
586564
}
587565

0 commit comments

Comments
 (0)