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-29665
This commit is contained in:
Sam Brannen 2022-12-08 18:17:41 -05:00
parent 404661d5cb
commit 52af5c2b38
2 changed files with 85 additions and 29 deletions

View File

@ -22,6 +22,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.StringJoiner;
import org.springframework.asm.MethodVisitor; import org.springframework.asm.MethodVisitor;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
@ -46,10 +47,15 @@ import org.springframework.util.Assert;
* Represents the invocation of a constructor. Either a constructor on a regular type or * Represents the invocation of a constructor. Either a constructor on a regular type or
* construction of an array. When an array is constructed, an initializer can be specified. * construction of an array. When an array is constructed, an initializer can be specified.
* *
* <p>Examples:<br> * <h4>Examples</h4>
* new String('hello world')<br> * <ul>
* new int[]{1,2,3,4}<br> * <li><code>new example.Foo()</code></li>
* new int[3] new int[3]{1,2,3} * <li><code>new String('hello world')</code></li>
* <li><code>new int[] {1,2,3,4}</code></li>
* <li><code>new String[] {'abc','xyz'}</code></li>
* <li><code>new int[5]</code></li>
* <li><code>new int[3][4]</code></li>
* </ul>
* *
* @author Andy Clement * @author Andy Clement
* @author Juergen Hoeller * @author Juergen Hoeller
@ -68,7 +74,7 @@ public class ConstructorReference extends SpelNodeImpl {
private final boolean isArrayConstructor; private final boolean isArrayConstructor;
@Nullable @Nullable
private SpelNodeImpl[] dimensions; private final SpelNodeImpl[] dimensions;
// TODO is this caching safe - passing the expression around will mean this executor is also being passed around // TODO is this caching safe - passing the expression around will mean this executor is also being passed around
/** The cached executor that may be reused on subsequent evaluations. */ /** The cached executor that may be reused on subsequent evaluations. */
@ -83,6 +89,7 @@ public class ConstructorReference extends SpelNodeImpl {
public ConstructorReference(int startPos, int endPos, SpelNodeImpl... arguments) { public ConstructorReference(int startPos, int endPos, SpelNodeImpl... arguments) {
super(startPos, endPos, arguments); super(startPos, endPos, arguments);
this.isArrayConstructor = false; this.isArrayConstructor = false;
this.dimensions = null;
} }
/** /**
@ -214,16 +221,33 @@ public class ConstructorReference extends SpelNodeImpl {
@Override @Override
public String toStringAST() { public String toStringAST() {
StringBuilder sb = new StringBuilder("new "); StringBuilder sb = new StringBuilder("new ");
int index = 0; sb.append(getChild(0).toStringAST()); // constructor or array type
sb.append(getChild(index++).toStringAST());
sb.append('('); // Arrays
for (int i = index; i < getChildCount(); i++) { if (this.isArrayConstructor) {
if (i > index) { if (hasInitializer()) {
sb.append(','); // new int[] {1, 2, 3, 4, 5}, etc.
InlineList initializer = (InlineList) getChild(1);
sb.append("[] ").append(initializer.toStringAST());
}
else {
// new int[3], new java.lang.String[3][4], etc.
for (SpelNodeImpl dimension : this.dimensions) {
sb.append('[').append(dimension.toStringAST()).append(']');
}
} }
sb.append(getChild(i).toStringAST());
} }
sb.append(')'); // Constructors
else {
// new String('hello'), new org.example.Person('Jane', 32), etc.
StringJoiner sj = new StringJoiner(",", "(", ")");
int count = getChildCount();
for (int i = 1; i < count; i++) {
sj.add(getChild(i).toStringAST());
}
sb.append(sj.toString());
}
return sb.toString(); return sb.toString();
} }

View File

@ -67,37 +67,31 @@ class ParsingTests {
parseCheck("#var1='value1'"); parseCheck("#var1='value1'");
} }
@Disabled("toStringAST() is broken for array construction")
@Test @Test
void collectionProcessorsCountStringArray() { void collectionProcessorsCountStringArray() {
parseCheck("new String[] {'abc','def','xyz'}.count()"); parseCheck("new String[] {'abc','def','xyz'}.count()");
} }
@Disabled("toStringAST() is broken for array construction")
@Test @Test
void collectionProcessorsCountIntArray() { void collectionProcessorsCountIntArray() {
parseCheck("new int[] {1,2,3}.count()"); parseCheck("new int[] {1,2,3}.count()");
} }
@Disabled("toStringAST() is broken for array construction")
@Test @Test
void collectionProcessorsMax() { void collectionProcessorsMax() {
parseCheck("new int[] {1,2,3}.max()"); parseCheck("new int[] {1,2,3}.max()");
} }
@Disabled("toStringAST() is broken for array construction")
@Test @Test
void collectionProcessorsMin() { void collectionProcessorsMin() {
parseCheck("new int[] {1,2,3}.min()"); parseCheck("new int[] {1,2,3}.min()");
} }
@Disabled("toStringAST() is broken for array construction")
@Test @Test
void collectionProcessorsAverage() { void collectionProcessorsAverage() {
parseCheck("new int[] {1,2,3}.average()"); parseCheck("new int[] {1,2,3}.average()");
} }
@Disabled("toStringAST() is broken for array construction")
@Test @Test
void collectionProcessorsSort() { void collectionProcessorsSort() {
parseCheck("new int[] {3,2,1}.sort()"); parseCheck("new int[] {3,2,1}.sort()");
@ -444,32 +438,70 @@ class ParsingTests {
@Test @Test
void methods() { void methods() {
parseCheck("echo()");
parseCheck("echo(12)"); parseCheck("echo(12)");
parseCheck("echo(name)"); parseCheck("echo(name)");
parseCheck("echo('Jane')");
parseCheck("echo('Jane',32)");
parseCheck("echo('Jane', 32)", "echo('Jane',32)");
parseCheck("age.doubleItAndAdd(12)"); parseCheck("age.doubleItAndAdd(12)");
} }
@Test @Test
void constructors() { void constructorWithNoArguments() {
parseCheck("new String('hello')"); parseCheck("new Foo()");
parseCheck("new example.Foo()");
} }
@Disabled("toStringAST() is broken for array construction")
@Test @Test
void arrayConstruction01() { void constructorWithOneArgument() {
parseCheck("new String('hello')");
parseCheck("new String( 'hello' )", "new String('hello')");
parseCheck("new String(\"hello\" )", "new String('hello')");
}
@Test
void constructorWithMultipleArguments() {
parseCheck("new example.Person('Jane',32,true)");
parseCheck("new example.Person('Jane', 32, true)", "new example.Person('Jane',32,true)");
parseCheck("new example.Person('Jane', 2 * 16, true)", "new example.Person('Jane',(2 * 16),true)");
}
@Test
void arrayConstructionWithOneDimensionalReferenceType() {
parseCheck("new String[3]"); parseCheck("new String[3]");
} }
@Disabled("toStringAST() is broken for array construction")
@Test @Test
void arrayConstruction02() { void arrayConstructionWithOneDimensionalFullyQualifiedReferenceType() {
parseCheck("new int[] {1, 2, 3, 4, 5}", "new int[] {1,2,3,4,5}"); parseCheck("new java.lang.String[3]");
} }
@Disabled("toStringAST() is broken for array construction")
@Test @Test
void arrayConstruction03() { void arrayConstructionWithOneDimensionalPrimitiveType() {
parseCheck("new String[] {'abc','xyz'}", "new String[] {'abc','xyz'}"); parseCheck("new int[3]");
}
@Test
void arrayConstructionWithMultiDimensionalReferenceType() {
parseCheck("new Float[3][4]");
}
@Test
void arrayConstructionWithMultiDimensionalPrimitiveType() {
parseCheck("new int[3][4]");
}
@Test
void arrayConstructionWithOneDimensionalReferenceTypeWithInitializer() {
parseCheck("new String[] {'abc','xyz'}");
parseCheck("new String[] {'abc', 'xyz'}", "new String[] {'abc','xyz'}");
}
@Test
void arrayConstructionWithOneDimensionalPrimitiveTypeWithInitializer() {
parseCheck("new int[] {1,2,3,4,5}");
parseCheck("new int[] {1, 2, 3, 4, 5}", "new int[] {1,2,3,4,5}");
} }
} }