SpEL inline lists and maps look like ${ {1, 2, 3} } and ${ {'a': 1, 'b': 2} }.
The placeholder scanner, however, stopped at the first closing brace it saw.
The old implementation used a regular expression that forbade nested braces.
That was fine for ${user.name} and broke the moment an expression legitimately
contained its own braces. An inline map would be truncated at its first }, and
the rest of the expression leaked out as literal text.
PlaceholderHooker now scans left-to-right for the opening delimiter, then
balances braces instead of stopping at the first close:
int depth = 1;
// each '{' increments, each '}' decrements;
// the placeholder ends when depth reaches 0
A placeholder now ends at the brace that balances its opening brace:
${ {1, 2, 3} } -> expression " {1, 2, 3} "
${ {'a': 1, 'b': 2} } -> expression " {'a': 1, 'b': 2} "
A placeholder whose opening brace is never balanced is captured verbatim and handed to the configured exception resolver, so a broken template fails loudly instead of silently emitting half an expression.
When a parser’s job is to find the balanced end of a construct, a regex is usually the wrong tool. Count the depth instead.
Related issue: #796.