List free variables in an EL expression
-
24-06-2021 - |
Question
I have an application which contains some EL evaluation used for programmatic configuration. Given an EL expression I want to get what free variables it contains without actually evaluating it. The intention is to provide a UI where end users can bind values to free variables before pressing an "evaluate" button.
Unfortunately javax.el.ValueExpression
does not provide this functionality, so I may need to use vendor-specific API. It is quite early in the development, so I have not yet fixed my choice of implementation. I have thought of MVEL, JUEL and SpEL, but of course whatever I chose should have the functionality I described above.
Solution
MVEL's ParserContext can tell you all about the variables organized by locals and inputs.
ParserContext ctx = ParserContext.create();
MVEL.analysisCompile("a = 0; b = 0;", ctx);
HashMap<String, Class> vars = ctx.getVariables();
assert vars.containsKey("a") && Number.class.isAssignableFrom(vars.get("a"));
assert vars.containsKey("b") && Number.class.isAssignableFrom(vars.get("b"));
OTHER TIPS
How about this...
SpelExpression parseExpression = (SpelExpression) new SpelExpressionParser().parseExpression(expressionString);
SpelNode node = parseExpression.getAST();
List<String> vars = getVars(node);
...
private List<String> getVars(SpelNode node) {
List<String> vars = new ArrayList<String>();
for (int i = 0; i < node.getChildCount(); i++) {
SpelNode child = node.getChild(i);
if (child.getChildCount() > 0) {
vars.addAll(getVars(child));
}
else {
if (child instanceof VariableReference) {
vars.add(child.toStringAST());
}
}
}
return vars;
}
Gary's answer is good but it didn't work for me when the expression contained a single variable, e.g. "#var" (one node with no children). A small change:
private Set<String> getVars(SpelNode node) {
Set<String> vars = new HashSet<String>();
if (node == null) {
return vars;
}
if (node instanceof VariableReference) {
// Remove the "#" to get the actual variable name
vars.add(StringUtils.remove(node.toStringAST(), "#"));
}
for (int i = 0; i < node.getChildCount(); i++) {
SpelNode child = node.getChild(i);
vars.addAll(getVars(child));
}
return vars;
}