/*- * Copyright © 2009 Diamond Light Source Ltd. * * This file is part of GDA. * * GDA is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License version 3 as published by the Free * Software Foundation. * * GDA is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along * with GDA. If not, see <http://www.gnu.org/licenses/>. */ package uk.ac.gda.richbeans.editors; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.jface.fieldassist.ContentProposalAdapter; import org.eclipse.richbeans.api.event.ValueEvent; import org.eclipse.richbeans.api.event.ValueListener; import org.eclipse.richbeans.api.widget.IExpressionManager; import org.eclipse.richbeans.api.widget.IExpressionWidget; import org.eclipse.richbeans.api.widget.IFieldProvider; import org.eclipse.richbeans.api.widget.IFieldWidget; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.widgets.Control; import org.nfunk.jep.ASTVarNode; import org.nfunk.jep.JEP; import org.nfunk.jep.Node; import org.nfunk.jep.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.gda.util.list.SortNatural; /** * Allows the expression only to be a function of field values of fields in the bean passed in. */ public class BeanExpressionManager implements IExpressionManager, ValueListener { @SuppressWarnings("unused") private static final Logger logger = LoggerFactory.getLogger(BeanExpressionManager.class); private double value; private boolean expressionValid; private String expression; private IFieldProvider provider; private IExpressionWidget widget; private Collection<String> fields; private JEP jepParser; private ExpressionContentProposalProvider proposalProvider; private List<IFieldWidget> precedents = new ArrayList<IFieldWidget>(7); public BeanExpressionManager(final IExpressionWidget widget, IFieldProvider prov) { this.widget = widget; this.provider = prov; // The parser which will check expressions. this.jepParser = new JEP(); jepParser.addStandardFunctions(); jepParser.addStandardConstants(); jepParser.setAllowUndeclared(false); jepParser.setImplicitMul(true); } @Override public double getExpressionValue() { return value; } @Override public String getExpression() { return expression; } @Override public void setExpression(String expression) { this.expression = expression; try { if (expression == null) { value = Double.NaN; expressionValid = false; } this.value = calculateValue(); this.expressionValid = !Double.isNaN(value) && !Double.isInfinite(value); updateListeners(); } catch (Exception ne) { clearListeners(); value = Double.NaN; expressionValid = false; } } /** * Removes old listeners and adds new ones. * * @throws ParseException */ private void updateListeners() throws Exception { clearListeners(); if (!expressionValid) return; // NOTE newer versions of JEP have TreeAnalyzer for this... final Node node = jepParser.parse(expression); parseNode(node, precedents); for (IFieldWidget wid : precedents) { wid.addValueListener(this); } } private void clearListeners() { for (IFieldWidget widget : precedents) { try { widget.removeValueListener(this); } catch (Throwable ignored) { // Intentional, keep trying to clear the listeners. } } precedents.clear(); } @Override public void valueChangePerformed(ValueEvent e) { if (e.getFieldName() == null) return; jepParser.addVariable(e.getFieldName(), e.getDoubleValue()); this.value = jepParser.getValue(); widget.setExpressionValue(value); } /** * Recursive node parsing * * @param node * @param precedents * @throws Exception */ private void parseNode(Node node, List<IFieldWidget> precedents) throws Exception { if (node instanceof ASTVarNode) { IFieldWidget widget = getWidget((ASTVarNode) node); if (widget != null) precedents.add(widget); return; } for (int i = 0; i < node.jjtGetNumChildren(); ++i) { final Node c = node.jjtGetChild(i); parseNode(c, precedents); } } private IFieldWidget getWidget(ASTVarNode node) { try { return provider.getField(node.getName()); } catch (Exception ne) { return null; } } private double calculateValue() { for (String field : fields) { Object value; try { value = provider.getFieldValue(field); } catch (Exception e) { continue; } if (!(value instanceof Number)) continue; jepParser.addVariable(field, ((Number) value).doubleValue()); } jepParser.parseExpression(getExpression()); return jepParser.getValue(); } @Override public Collection<String> getAllowedSymbols() throws Exception { return fields; } /** * Assumes that the fields are already sorted. */ @Override public void setAllowedSymbols(final Collection<String> fs) throws Exception { // Remove duplicates final Set<String> fieldSet = new HashSet<String>(fs); // Remove self if (widget.getFieldName() != null) fieldSet.remove(widget.getFieldName()); // Sort list final List<String> fieldList = new ArrayList<String>(fieldSet.size()); fieldList.addAll(fieldSet); Collections.sort(fieldList, new SortNatural<String>(false)); fields = fieldList; // Create content helper final String[] fieldsArray = fields.toArray(new String[fields.size()]); if ((widget.getControl()) instanceof StyledText && widget.isExpressionAllowed()) { if (proposalProvider == null) { proposalProvider = new ExpressionContentProposalProvider(fieldsArray, widget); ContentProposalAdapter adapter = new ContentProposalAdapter((Control) widget.getControl(), new StyledTextContentAdapter(), proposalProvider, null, null); adapter.setLabelProvider(new ExpressionLabelProvider(fields)); adapter.setPropagateKeys(true); adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE); } else { proposalProvider.setProposals(fieldsArray); } } } @Override public boolean isExpressionValid() { return expressionValid; } @Override public String getValueListenerName() { return widget.getFieldName() + " " + getClass().getName(); } }