Java tutorial
/* * Copyright (c) 2012 Diamond Light Source Ltd. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.dawnsci.jexl.internal; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.jexl2.Expression; import org.apache.commons.jexl2.JexlEngine; import org.apache.commons.jexl2.JexlException; import org.apache.commons.jexl2.MapContext; import org.apache.commons.jexl2.Script; import org.eclipse.dawnsci.analysis.api.expressions.ExpressionEngineEvent; import org.eclipse.dawnsci.analysis.api.expressions.IExpressionEngine; import org.eclipse.dawnsci.analysis.api.expressions.IExpressionEngineListener; import org.eclipse.dawnsci.analysis.api.monitor.IMonitor; import org.eclipse.dawnsci.analysis.dataset.impl.Image; import org.eclipse.dawnsci.analysis.dataset.impl.Maths; public class ExpressionEngineImpl implements IExpressionEngine { private JexlEngine jexl; private Expression expression; private MapContext context; private HashSet<IExpressionEngineListener> expressionListeners; private Callable<Object> callable; public ExpressionEngineImpl() { //Create the Jexl engine with the DatasetArthmetic object to allows basic //mathematical calculations to be performed on Datasets jexl = new JexlEngine(null, new DatasetArithmetic(false), null, null); //Add some useful functions to the engine Map<String, Object> funcs = new HashMap<String, Object>(); funcs.put("dnp", Maths.class); funcs.put("dat", JexlGeneralFunctions.class); funcs.put("im", Image.class); funcs.put("lz", JexlLazyFunctions.class); //TODO determine which function classes should be loaded as default // funcs.put("fft", FFT.class); // funcs.put("plt", SDAPlotter.class); jexl.setFunctions(funcs); expressionListeners = new HashSet<IExpressionEngineListener>(); } @Override public void createExpression(String expr) throws Exception { this.expression = jexl.createExpression(expr); checkFunctions(expr); } /** * TODO FIXME Must be better way than this... * @param expr * @throws Exception */ private void checkFunctions(String expr) throws Exception { // We do not support the . operator for now because // otherwise http://jira.diamond.ac.uk/browse/SCI-1731 //if (expr.indexOf('.')>-1) { // throw new Exception("The dot operator '.' is not supported."); //} // We now evaluate the expression to try and trap invalid functions. try { final Script script = jexl.createScript(expr); Set<List<String>> names = script.getVariables(); Collection<String> vars = unpack(names); final Map<String, Object> dummy = new HashMap<String, Object>(vars.size()); for (String name : vars) dummy.put(name, 1); MapContext dCnxt = new MapContext(dummy); expression.evaluate(dCnxt); } catch (JexlException ne) { if (ne.getMessage().toLowerCase().contains("no such function namespace")) { final String msg = ne.toString(); final String[] segs = msg.split(":"); throw new Exception(segs[3], ne); } } catch (Exception ignored) { // We allow the expression but it might fail later } } @Override public Object evaluate() throws Exception { checkAndCreateContext(); return expression.evaluate(context); } @Override public void addLoadedVariables(Map<String, Object> variables) { if (context == null) { context = new MapContext(variables); return; } for (String name : variables.keySet()) { context.set(name, variables.get(name)); } } @Override public void addLoadedVariable(String name, Object value) { checkAndCreateContext(); context.set(name, value); } @Override public Map<String, Object> getFunctions() { return jexl.getFunctions(); } @Override public void setFunctions(Map<String, Object> functions) { jexl.setFunctions(functions); } @Override public void setLoadedVariables(Map<String, Object> variables) { context = new MapContext(variables); } @Override public Collection<String> getVariableNamesFromExpression() { try { final Script script = jexl.createScript(expression.getExpression()); Set<List<String>> names = script.getVariables(); return unpack(names); } catch (Exception e) { return null; } } private Collection<String> unpack(Set<List<String>> dottednames) { Collection<String> ret = new LinkedHashSet<String>(); for (List<String> dottedname : dottednames) { if (dottedname.size() == 0) { continue; } else if (dottedname.size() == 1) { ret.add(dottedname.get(0)); } else { StringBuilder name = new StringBuilder(); for (String namePart : dottedname) { name.append(namePart); name.append("."); } String assembledName = name.substring(0, name.length() - 1); ret.add(assembledName); } } return ret; } /** * TODO FIXME Currently does a regular expression. If there was a * way in JEXL of getting the variables for a given function it would * be better than what we do: which is a regular expression! * * lz:rmean(fred,0) -> fred * 10*lz:rmean(fred,0) -> fred * 10*lz:rmean(fred,0)+dat:mean(bill,0) -> fred * lz:rmean(fred,0)+lz:rmean(bill,0) -> fred, bill * lz:func(fred*10,bill,ted*2) -> fred, bill, ted */ @Override public Collection<String> getLazyVariableNamesFromExpression() { try { final String expr = expression.getExpression(); return getLazyVariables(expr); } catch (Exception e) { return null; } } private static final Pattern lazyPattern = Pattern .compile("lz\\:[a-zA-Z0-9_]+\\({1}([a-zA-Z0-9_ \\,\\*\\+\\-\\\\]+)\\){1}"); private Collection<String> getLazyVariables(String expr) { final Collection<String> found = new HashSet<String>(4); final Matcher matcher = lazyPattern.matcher(expr); while (matcher.find()) { final String exprLine = matcher.group(1); final Script script = jexl.createScript(exprLine.replace(',', '+')); Set<List<String>> names = script.getVariables(); Collection<String> v = unpack(names); found.addAll(v); } return found; } public static void main(String[] args) throws Exception { test(""); test("lz:rmean(fred,0)", "fred"); test("10*lz:rmean(fred,0)", "fred"); test("10*lz:rmean(fred,0)+dat:mean(bill,0)", "fred"); test("10*lz:rmean(larry,0)+dat:mean(lz,0)", "larry"); test("10*lz:rmean(larry,0)+lz:mean(lz,0)", "larry", "lz"); test("lz:rmean(fred,0)+lz:rmean(bill,0)", "fred", "bill"); test("lz:func(fred*10,bill,ted*2)", "fred", "bill", "ted"); test("lz:func(fred*10,bill,ted*2)+lz:func(p+10,q+20,r-2)", "fred", "bill", "ted", "p", "q", "r"); // TODO FIXME This means you cannot use brackets inside lz:xxx( ... ) //test("lz:func(sin(fred)*10,bill,ted*2)", "fred", "bill", "ted"); } private static void test(String expr, String... vars) throws Exception { ExpressionEngineImpl impl = new ExpressionEngineImpl(); final Collection<String> found = impl.getLazyVariables(expr); if (found.size() != vars.length) throw new Exception("Incorrect number of variables found!"); if (!found.containsAll(Arrays.asList(vars))) throw new Exception("Incorrect number of variables found!"); System.out.println(found); System.out.println(">>>> Ok <<<<"); } private void checkAndCreateContext() { if (context == null) { context = new MapContext(); } } @Override public void addExpressionEngineListener(IExpressionEngineListener listener) { expressionListeners.add(listener); } @Override public void removeExpressionEngineListener(IExpressionEngineListener listener) { expressionListeners.remove(listener); } @Override public void evaluateWithEvent(IMonitor mon) { if (expression == null) return; final Script script = jexl.createScript(expression.getExpression()); checkAndCreateContext(); callable = script.callable(context); final ExecutorService service = Executors.newCachedThreadPool(); //final IMonitor monitor = mon == null ? new IMonitor.Stub() : mon; service.submit(new Runnable() { @Override public void run() { String exp = expression.getExpression(); try { Future<Object> future = service.submit(callable); Object result = future.get(); ExpressionEngineEvent event = new ExpressionEngineEvent(ExpressionEngineImpl.this, result, exp); fireExpressionListeners(event); return; } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Exception e) { ExpressionEngineEvent event = new ExpressionEngineEvent(ExpressionEngineImpl.this, e, exp); fireExpressionListeners(event); } ExpressionEngineEvent event = new ExpressionEngineEvent(ExpressionEngineImpl.this, null, exp); fireExpressionListeners(event); return; } }); } private void fireExpressionListeners(ExpressionEngineEvent event) { for (IExpressionEngineListener listener : expressionListeners) { listener.calculationDone(event); } } @Override public Object getLoadedVariable(String name) { if (context == null) return null; return context.get(name); } }