Reputation: 421
Having the following JEXL expression:
String expression = "myVar >= 12345 && mySecondVar <= 56789";
I can call createScript and getVariables to get myVar and mySecondVar as values, such as:
Set<List<String>> expressionVars = JEXL.createScript(expression).getVariables();
What I would like to know is, if given the same expression, I could call some other method that would return the values for those variables. Reason being is that I would like to validate the input of some of those values. I checked the docs and played around with JexlScript class but can't find an elegant way of doing it. As JEXL is already doing the work of parsing my expression, it would be awesome to be able to retrieve this info, and not having to manually parse my expression to get this values.
Something in the line of script.getValue("myVar");
returning 12345
Upvotes: 0
Views: 8457
Reputation: 364
With JEXL, you evaluate a script/expression in a given context (JexlContext) that holds the variables and their values. JexlContext exposes the 'has' and 'get' methods that respectively check for the existence and get the value of variables. In your case, you need to find out what your JexlContext is (or should be); from there, it is straightforward to iterate on your variables (extracted from your script) and check their values (from the context).
See: http://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/JexlContext.html http://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/JexlScript.html
For example (using JEXL 3.2 trunk from https://github.com/apache/commons-jexl):
/**
* Collect the global variables of a script and their values from a context.
* @param script the script
* @param context the context
* @return a map keyed by variable name of their contextual values
*/
Map<String, Object> collectVars(JexlScript script, JexlContext context) {
Set<List<String>> sls = script.getVariables();
Map<String, Object> vars = new TreeMap<String, Object>();
for(List<String> ls : sls) {
// build the 'antish' name by concatenating
StringBuilder strb = new StringBuilder();
for(String s : ls) {
if (strb.length() > 0) {
strb.append('.');
}
strb.append(s);
}
String name = strb.toString();
vars.put(name, context.get(name));
}
return vars;
}
@Test
public void testStckOvrflw() throws Exception {
JexlEngine jexl = new JexlBuilder().safe(false).create();
// a context with some variables
JexlContext context = new MapContext();
context.set("low", 15000);
context.set("high", 50000);
context.set("mid", 35000);
context.set("limit.low", 15042);
context.set("limit.high", 35042);
// an expression with 2 variables
JexlScript expr = jexl.createScript("low >= 12345 && high <= 56789");
// collecting the 2 variables, low and high
Map<String, Object> vars = collectVars(expr, context);
Assert.assertEquals(2, vars.size());
Assert.assertEquals(15000, vars.get("low"));
Assert.assertEquals(50000, vars.get("high"));
expr = jexl.createScript("limit.low >= 12345 && limit.high <= 56789");
vars = collectVars(expr, context);
Assert.assertEquals(2, vars.size());
Assert.assertEquals(15042, vars.get("limit.low"));
Assert.assertEquals(35042, vars.get("limit.high"));
}
Upvotes: 2
Reputation: 21674
You should implement your own context:
public class ZenContext implements JexlContext {
static private final Map<String, Object> reservedVars = new HashMap<String, Object>();
private final Map<String, Object> scriptDefinedVars = new HashMap<String, Object>();
static {
reservedVars.put("math", java.lang.Math.class);
reservedVars.put("stats", apache.commons.math3.stats.Stats);
// whatever else ...
}
public boolean has(String name) {
if (reservedVars .get(name) != null) return true;
return scriptDefinedVars.get(name) != null;
}
public boolean get (String name) {
Object value = null;
if ((value = reservedVars .get(name)) != null) return value;
return scriptDefinedVars.get(name);
}
public void set(String name, Object value) {
scriptDefinedVars.set(name, value);
}
public Map<String, Object> getReservedVars () {
return reservedVars;
}
public Map<String, Object> getScriptDefinedVars () {
return scriptDefinedVars ;
}
}
In this way, you would have
And then add these methods.
public Object execute(File scriptFile) {
JexlScript script = jexlEngine.createScript(scriptFile);
return script.execute(this); // supply this as the context
}
public Object execute (String scriptText) {
JexlScript script = jexlEngine.createScript(scriptText);
return script.execute(this); // supply this as the context
}
You could modify the set method I wrote here, to inspect the variables in the maps, before allowing them to set.
However, this will not work for local script vars
var greeting = 'The rain in Maine falls plainly insane';
Because local var relies on a different mechanism, org.apache.commons.jexl3.internal.Scope, using the getLocalVariables() method, which has a bug.
Upvotes: 1