Skip to content

Commit 7228503

Browse files
committed
Support arrays in AST string representations of SpEL expressions
Prior to this commit, SpEL's ConstructorReference did not provide support for arrays when generating a string representation of the internal AST. For example, 'new String[3]' was represented as 'new String()' instead of 'new String[3]'. This commit introduces support for standard array construction and array construction with initializers in ConstructorReference's toStringAST() implementation. Closes gh-29666
1 parent 9334740 commit 7228503

File tree

2 files changed

+84
-28
lines changed

2 files changed

+84
-28
lines changed

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

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.lang.reflect.Modifier;
2323
import java.util.ArrayList;
2424
import java.util.List;
25+
import java.util.StringJoiner;
2526

2627
import org.springframework.asm.MethodVisitor;
2728
import org.springframework.core.convert.TypeDescriptor;
@@ -46,10 +47,15 @@
4647
* Represents the invocation of a constructor. Either a constructor on a regular type or
4748
* construction of an array. When an array is constructed, an initializer can be specified.
4849
*
49-
* <p>Examples:<br>
50-
* new String('hello world')<br>
51-
* new int[]{1,2,3,4}<br>
52-
* new int[3] new int[3]{1,2,3}
50+
* <h4>Examples</h4>
51+
* <ul>
52+
* <li><code>new example.Foo()</code></li>
53+
* <li><code>new String('hello world')</code></li>
54+
* <li><code>new int[] {1,2,3,4}</code></li>
55+
* <li><code>new String[] {'abc','xyz'}</code></li>
56+
* <li><code>new int[5]</code></li>
57+
* <li><code>new int[3][4]</code></li>
58+
* </ul>
5359
*
5460
* @author Andy Clement
5561
* @author Juergen Hoeller
@@ -68,7 +74,7 @@ public class ConstructorReference extends SpelNodeImpl {
6874
private final boolean isArrayConstructor;
6975

7076
@Nullable
71-
private SpelNodeImpl[] dimensions;
77+
private final SpelNodeImpl[] dimensions;
7278

7379
// TODO is this caching safe - passing the expression around will mean this executor is also being passed around
7480
/** The cached executor that may be reused on subsequent evaluations. */
@@ -83,6 +89,7 @@ public class ConstructorReference extends SpelNodeImpl {
8389
public ConstructorReference(int startPos, int endPos, SpelNodeImpl... arguments) {
8490
super(startPos, endPos, arguments);
8591
this.isArrayConstructor = false;
92+
this.dimensions = null;
8693
}
8794

8895
/**
@@ -214,16 +221,33 @@ private ConstructorExecutor findExecutorForConstructor(String typeName,
214221
@Override
215222
public String toStringAST() {
216223
StringBuilder sb = new StringBuilder("new ");
217-
int index = 0;
218-
sb.append(getChild(index++).toStringAST());
219-
sb.append('(');
220-
for (int i = index; i < getChildCount(); i++) {
221-
if (i > index) {
222-
sb.append(',');
224+
sb.append(getChild(0).toStringAST()); // constructor or array type
225+
226+
// Arrays
227+
if (this.isArrayConstructor) {
228+
if (hasInitializer()) {
229+
// new int[] {1, 2, 3, 4, 5}, etc.
230+
InlineList initializer = (InlineList) getChild(1);
231+
sb.append("[] ").append(initializer.toStringAST());
232+
}
233+
else {
234+
// new int[3], new java.lang.String[3][4], etc.
235+
for (SpelNodeImpl dimension : this.dimensions) {
236+
sb.append('[').append(dimension.toStringAST()).append(']');
237+
}
223238
}
224-
sb.append(getChild(i).toStringAST());
225239
}
226-
sb.append(')');
240+
// Constructors
241+
else {
242+
// new String('hello'), new org.example.Person('Jane', 32), etc.
243+
StringJoiner sj = new StringJoiner(",", "(", ")");
244+
int count = getChildCount();
245+
for (int i = 1; i < count; i++) {
246+
sj.add(getChild(i).toStringAST());
247+
}
248+
sb.append(sj.toString());
249+
}
250+
227251
return sb.toString();
228252
}
229253

spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,37 +67,31 @@ void assignmentToVariables() {
6767
parseCheck("#var1='value1'");
6868
}
6969

70-
@Disabled("toStringAST() is broken for array construction")
7170
@Test
7271
void collectionProcessorsCountStringArray() {
7372
parseCheck("new String[] {'abc','def','xyz'}.count()");
7473
}
7574

76-
@Disabled("toStringAST() is broken for array construction")
7775
@Test
7876
void collectionProcessorsCountIntArray() {
7977
parseCheck("new int[] {1,2,3}.count()");
8078
}
8179

82-
@Disabled("toStringAST() is broken for array construction")
8380
@Test
8481
void collectionProcessorsMax() {
8582
parseCheck("new int[] {1,2,3}.max()");
8683
}
8784

88-
@Disabled("toStringAST() is broken for array construction")
8985
@Test
9086
void collectionProcessorsMin() {
9187
parseCheck("new int[] {1,2,3}.min()");
9288
}
9389

94-
@Disabled("toStringAST() is broken for array construction")
9590
@Test
9691
void collectionProcessorsAverage() {
9792
parseCheck("new int[] {1,2,3}.average()");
9893
}
9994

100-
@Disabled("toStringAST() is broken for array construction")
10195
@Test
10296
void collectionProcessorsSort() {
10397
parseCheck("new int[] {3,2,1}.sort()");
@@ -438,32 +432,70 @@ class MethodsConstructorsAndArrays {
438432

439433
@Test
440434
void methods() {
435+
parseCheck("echo()");
441436
parseCheck("echo(12)");
442437
parseCheck("echo(name)");
438+
parseCheck("echo('Jane')");
439+
parseCheck("echo('Jane',32)");
440+
parseCheck("echo('Jane', 32)", "echo('Jane',32)");
443441
parseCheck("age.doubleItAndAdd(12)");
444442
}
445443

446444
@Test
447-
void constructors() {
445+
void constructorWithNoArguments() {
446+
parseCheck("new Foo()");
447+
parseCheck("new example.Foo()");
448+
}
449+
450+
@Test
451+
void constructorWithOneArgument() {
448452
parseCheck("new String('hello')");
453+
parseCheck("new String( 'hello' )", "new String('hello')");
454+
parseCheck("new String(\"hello\" )", "new String('hello')");
455+
}
456+
457+
@Test
458+
void constructorWithMultipleArguments() {
459+
parseCheck("new example.Person('Jane',32,true)");
460+
parseCheck("new example.Person('Jane', 32, true)", "new example.Person('Jane',32,true)");
461+
parseCheck("new example.Person('Jane', 2 * 16, true)", "new example.Person('Jane',(2 * 16),true)");
449462
}
450463

451-
@Disabled("toStringAST() is broken for array construction")
452464
@Test
453-
void arrayConstruction01() {
465+
void arrayConstructionWithOneDimensionalReferenceType() {
454466
parseCheck("new String[3]");
455467
}
456468

457-
@Disabled("toStringAST() is broken for array construction")
458469
@Test
459-
void arrayConstruction02() {
460-
parseCheck("new int[] {1, 2, 3, 4, 5}", "new int[] {1,2,3,4,5}");
470+
void arrayConstructionWithOneDimensionalFullyQualifiedReferenceType() {
471+
parseCheck("new java.lang.String[3]");
472+
}
473+
474+
@Test
475+
void arrayConstructionWithOneDimensionalPrimitiveType() {
476+
parseCheck("new int[3]");
461477
}
462478

463-
@Disabled("toStringAST() is broken for array construction")
464479
@Test
465-
void arrayConstruction03() {
466-
parseCheck("new String[] {'abc','xyz'}", "new String[] {'abc','xyz'}");
480+
void arrayConstructionWithMultiDimensionalReferenceType() {
481+
parseCheck("new Float[3][4]");
482+
}
483+
484+
@Test
485+
void arrayConstructionWithMultiDimensionalPrimitiveType() {
486+
parseCheck("new int[3][4]");
487+
}
488+
489+
@Test
490+
void arrayConstructionWithOneDimensionalReferenceTypeWithInitializer() {
491+
parseCheck("new String[] {'abc','xyz'}");
492+
parseCheck("new String[] {'abc', 'xyz'}", "new String[] {'abc','xyz'}");
493+
}
494+
495+
@Test
496+
void arrayConstructionWithOneDimensionalPrimitiveTypeWithInitializer() {
497+
parseCheck("new int[] {1,2,3,4,5}");
498+
parseCheck("new int[] {1, 2, 3, 4, 5}", "new int[] {1,2,3,4,5}");
467499
}
468500
}
469501

0 commit comments

Comments
 (0)