// // FormulaVar.java // /* VisAD system for interactive analysis and visualization of numerical data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and Tommy Jasmin. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package visad.formula; import java.lang.reflect.InvocationTargetException; import java.rmi.RemoteException; import java.util.*; import visad.*; /** Represents a variable.<P> */ public class FormulaVar extends ActionImpl { /** for testing purposes */ public static boolean DEBUG = false; /** constant tag */ public static RealType CONSTANT = RealType.getRealType("visad/formula/constant"); /** associated FormulaManager object */ private FormulaManager fm; /** name of this variable */ String name; /** formula associated with this variable, if any */ private String formula; /** formula in postfix notation, if it has been converted */ private Postfix postfix; /** reference of this variable */ private ThingReference tref; /** list of errors that have occurred during computation, in String form */ private Vector errors = new Vector(); /** for synchronization */ private Object Lock = new Object(); /** whether the formula is currently being computed */ private boolean computing = false; /** constructor without specified ThingReference */ FormulaVar(String n, FormulaManager f) throws VisADException { this(n, f, null); } /** constructor with specified ThingReference */ FormulaVar(String n, FormulaManager f, ThingReference t) throws VisADException { super(n); fm = f; name = n; tref = (t == null) ? new ThingReferenceImpl(name) : t; for (int i=0; i<fm.bOps.length; i++) { if (name.indexOf(fm.bOps[i]) >= 0) { throw new FormulaException("variable names cannot contain operators"); } } for (int i=0; i<fm.uOps.length; i++) { if (name.indexOf(fm.uOps[i]) >= 0) { throw new FormulaException("variable names cannot contain operators"); } } } /** vector of variables on which this one depends */ private Vector depend = new Vector(); /** vector of variables that require this variable (depend on it) */ private Vector required = new Vector(); /** return whether any other variables depend on this one */ boolean othersDepend() { return !required.isEmpty(); } /** return whether this variable depends on v */ boolean isDependentOn(FormulaVar v) { if (v == this || depend.contains(v)) return true; for (int i=0; i<depend.size(); i++) { FormulaVar vi = (FormulaVar) depend.elementAt(i); if (vi.isDependentOn(v)) return true; } return false; } /** add a dependency for this variable */ private void setDependentOn(FormulaVar v) { if (!depend.contains(v)) { depend.add(v); try { addReference(v.getReference()); v.required.add(this); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); } } } /** clear this variable's dependency list */ private void clearDependencies() { int len = depend.size(); for (int i=0; i<len; i++) { FormulaVar v = (FormulaVar) depend.elementAt(i); v.required.remove(this); } depend.removeAllElements(); try { removeAllReferences(); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); } } /** rebuild this variable's dependency list, then recompute this variable */ private void rebuildDependencies() throws FormulaException { disableAction(); synchronized (Lock) { clearDependencies(); if (formula != null) { if (postfix == null) { try { // compute postfix expression Object[] o = new Object[2]; o[0] = formula; o[1] = fm; String pf = formula; try { // run formula through user's pre-processing function pf = (String) FormulaUtil.invokeMethod(fm.ppMethod, o); } catch (IllegalAccessException exc) { if (DEBUG) exc.printStackTrace(); evalError("Preparsing access exception", exc); } catch (IllegalArgumentException exc) { if (DEBUG) exc.printStackTrace(); evalError("Preparsing argument exception", exc); } catch (InvocationTargetException exc) { Throwable t = exc.getTargetException(); if (DEBUG) t.printStackTrace(); evalError("Preparsing exception", t); } postfix = new Postfix(pf, fm); int len = (postfix.tokens == null ? 0 : postfix.tokens.length); for (int i=0; i<len; i++) { String token = postfix.tokens[i]; if (postfix.codes[i] == Postfix.OTHER) { Double d = null; try { d = Double.valueOf(token); } catch (NumberFormatException exc) { } if (d == null) { // token is a variable name FormulaVar v = null; try { v = fm.getVarByNameOrCreate(token); } catch (FormulaException exc) { evalError("\"" + token + "\" is an illegal variable name"); } catch (VisADException exc) { evalError("Internal VisAD error", exc); } if (v != null) { if (v.isDependentOn(this)) { clearDependencies(); throw new FormulaException( "This formula creates an infinite loop"); } setDependentOn(v); } } } } } catch (FormulaException exc) { evalError("Syntax error in formula", exc); try { tref.setThing(null); } catch (VisADException exc2) { evalError("Internal VisAD error", exc2); } catch (RemoteException exc2) { evalError("Internal remote error", exc2); } } } } } enableAction(); if (depend.isEmpty()) { // formula has no dependencies; trigger doAction manually doAction(); } } /** set the formula for this variable */ void setFormula(String f) throws FormulaException { formula = f; if (textRef != null) { Text text = new Text(formula); try { textRef.setThing(text); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); } } postfix = null; computing = true; rebuildDependencies(); } /** waits for this formula to recompute */ void waitForFormula() { synchronized (Lock) { if (computing) { try { Lock.wait(); } catch (InterruptedException exc) { } } } } /** reference to a Text object equal to this variable's formula */ ThingReference textRef = null; /** cell for recomputing formula from referenced Text object */ CellImpl textCell = new CellImpl() { public void doAction() { boolean textChanged = false; if (textRef != null) { try { Thing thing = textRef.getThing(); if (thing instanceof Text) { Text t = (Text) thing; String newForm = t.getValue(); if (newForm == null) newForm = ""; if (!newForm.equals(formula)) { textChanged = true; setFormula(newForm); } } } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); } } } }; RemoteCellImpl rtCell = null; /** set the formula to be dependent on a Text object referenced by tr */ void setTextRef(ThingReference tr) throws VisADException, RemoteException { if (textRef == tr) return; if (textRef != null) { if (textRef instanceof RemoteDataReference) { rtCell.removeReference(textRef); rtCell = null; } else textCell.removeReference(textRef); } textRef = tr; if (textRef != null) { if (textRef instanceof RemoteDataReference) { rtCell = new RemoteCellImpl(textCell); rtCell.addReference(textRef); } else textCell.addReference(textRef); } } /** set the Thing for this variable directly */ void setThing(Thing t) throws VisADException, RemoteException { synchronized (Lock) { formula = null; postfix = null; clearDependencies(); if (t == null || t != tref.getThing()) tref.setThing(t); } } /** set the ThingReference for this variable */ void setReference(ThingReference tr) { if (tref == tr) return; synchronized (Lock) { int len = required.size(); for (int i=0; i<len; i++) { FormulaVar v = (FormulaVar) required.elementAt(i); try { v.removeReference(tref); v.addReference(tr); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); } } tref = tr; } } /** get the Thing for this variable */ Thing getThing() { try { return tref.getThing(); } catch (VisADException exc) { return null; } catch (RemoteException exc) { return null; } } /** get the ThingReference for this variable */ ThingReference getReference() { return tref; } /** get the formula for this variable */ String getFormula() { return formula; } /** get an array of Strings representing any errors that have occurred during formula evaluation */ String[] getErrors() { synchronized (errors) { int len = errors.size(); if (len == 0) return null; String[] s = new String[len]; for (int i=0; i<len; i++) { s[i] = (String) errors.elementAt(i); } return s; } } /** clear the list of errors that have occurred during formula evaluation */ void clearErrors() { synchronized (errors) { errors.clear(); } } /** add an error to the list of errors that have occurred during formula evaluation */ private void evalError(String s) { synchronized (errors) { errors.add(s); } } /** add an error to the list of errors that have occurred during formula evaluation, appending the given exception's message if any */ private void evalError(String s, Throwable t) { String msg = (t == null ? null : t.getMessage()); evalError(s + (msg == null ? "" : ": " + msg)); } /** recompute this variable */ public void doAction() { synchronized (Lock) { try { if (postfix != null) tref.setThing(compute(postfix)); } catch (VisADException exc) { evalError("Could not store final value in variable"); } catch (RemoteException exc) { evalError("Could not store final value in variable (remote)"); } computing = false; Lock.notifyAll(); } } /** used by compute method for convenience */ private Thing popStack(Stack s) { if (s.empty()) { evalError("Syntax error in formula (stack empty)"); return null; } else return (Thing) s.pop(); } /** compute the solution to this variable's postfix formula */ private Thing compute(Postfix formula) { if (formula.tokens == null) return null; int len = formula.tokens.length; Stack stack = new Stack(); for (int i=0; i<len; i++) { String token = formula.tokens[i]; String op = "\"" + token + "\""; int code = formula.codes[i]; if (code == Postfix.BINARY) { Object[] o = new Object[2]; o[1] = popStack(stack); o[0] = popStack(stack); Thing ans = null; if (o[0] != null && o[1] != null) { for (int j=0; j<fm.bMethods.length; j++) { // support for overloaded operators if (ans == null && fm.bOps[j].equals(token)) { try { ans = (Thing) FormulaUtil.invokeMethod(fm.bMethods[j], o); } catch (IllegalAccessException exc) { if (DEBUG) exc.printStackTrace(); evalError( "Cannot access binary method for operator " + op, exc); } // no access catch (IllegalArgumentException exc) { if (DEBUG) exc.printStackTrace(); evalError( "Invalid argument to binary method for operator " + op, exc); } // wrong type of method catch (InvocationTargetException exc) { Throwable t = exc.getTargetException(); if (DEBUG) t.printStackTrace(); evalError("Binary method for operator " + op + " threw an exception", t); } // method threw exception } } } if (ans == null) { evalError("Could not evaluate binary operator " + op); stack.push(null); } else stack.push(ans); } else if (code == Postfix.UNARY) { Object[] o = new Object[1]; o[0] = popStack(stack); Thing ans = null; if (o[0] != null) { for (int j=0; j<fm.uMethods.length; j++) { // support for overloaded operators if (ans == null && fm.uOps[j].equals(token)) { try { ans = (Thing) FormulaUtil.invokeMethod(fm.uMethods[j], o); } catch (IllegalAccessException exc) { if (DEBUG) exc.printStackTrace(); evalError( "Cannot access unary method for operator " + op, exc); } // no access catch (IllegalArgumentException exc) { if (DEBUG) exc.printStackTrace(); evalError( "Invalid argument to unary method for operator " + op, exc); } // wrong type of method catch (InvocationTargetException exc) { Throwable t = exc.getTargetException(); if (DEBUG) t.printStackTrace(); evalError("Unary method for operator " + op + " threw an exception", t); } // method threw exception } } } if (ans == null) { evalError("Could not evaluate unary operator " + op); stack.push(null); } else stack.push(ans); } else if (code == Postfix.FUNC) { Thing ans = null; if (fm.isFunction(token)) { // defined function - token is the function name int num = -1; try { Real r = (Real) popStack(stack); num = (int) r.getValue(); } catch (ClassCastException exc) { if (DEBUG) exc.printStackTrace(); } if (num < 0) { evalError("Syntax error in formula (invalid function arg length)"); num = 1; } Object[] o; if (num > 0) o = new Object[num]; else o = null; boolean eflag = false; for (int j=num-1; j>=0; j--) { o[j] = popStack(stack); if (o[j] == null) eflag = true; } if (!eflag) { for (int j=0; j<fm.funcs.length; j++) { // support for overloaded defined functions if (ans == null && fm.funcs[j].equalsIgnoreCase(token)) { try { ans = (Thing) FormulaUtil.invokeMethod(fm.fMethods[j], o); } catch (IllegalAccessException exc) { if (DEBUG) exc.printStackTrace(); evalError("Cannot access method for function " + op, exc); } // no access catch (IllegalArgumentException exc) { if (DEBUG) exc.printStackTrace(); evalError( "Invalid argument to method for function " + op, exc); } // wrong type of method catch (InvocationTargetException exc) { Throwable t = exc.getTargetException(); if (DEBUG) t.printStackTrace(); evalError( "Method for function " + op + " threw an exception", t); } // method threw exception } } } if (ans == null) { evalError("Could not evaluate function " + op); stack.push(null); } else stack.push(ans); } else { // implicit function - token is a non-negative integer int num = 0; try { num = Integer.parseInt(token) + 1; } catch (NumberFormatException exc) { if (DEBUG) exc.printStackTrace(); } if (num <= 0) { evalError("Syntax error in formula (invalid implicit arg length)"); num = 1; } Object[] o = new Object[num]; boolean eflag = false; for (int j=num-1; j>=0; j--) { o[j] = popStack(stack); if (o[j] == null) eflag = true; } if (!eflag) { for (int j=0; j<fm.iMethods.length; j++) { // support for overloaded implicit functions if (ans == null) { try { ans = (Thing) FormulaUtil.invokeMethod(fm.iMethods[j], o); } catch (IllegalAccessException exc) { if (DEBUG) exc.printStackTrace(); evalError("Cannot access method for implicit function", exc); } // no access catch (IllegalArgumentException exc) { if (DEBUG) exc.printStackTrace(); evalError( "Invalid argument to method for implicit function", exc); } // wrong type of method catch (InvocationTargetException exc) { Throwable t = exc.getTargetException(); if (DEBUG) t.printStackTrace(); evalError( "Method for implicit function threw an exception", t); } // method threw exception } } } if (ans == null) { evalError("Could not evaluate implicit function"); stack.push(null); } else stack.push(ans); } } else { // code == Postfix.OTHER Double d = null; try { d = Double.valueOf(token); } catch (NumberFormatException exc) { } if (d == null) { // token is a variable name FormulaVar v = null; try { v = fm.getVarByNameOrCreate(token); } catch (FormulaException exc) { evalError(op + " is an illegal variable name"); stack.push(null); } catch (VisADException exc) { evalError("Internal error", exc); stack.push(null); } if (v != null) { ThingReference r = v.getReference(); Thing t = null; if (r != null) { try { t = r.getThing(); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); } } if (t == null) { evalError("Variable " + op + " has no value"); stack.push(null); } else stack.push(t); } } else { // token is a constant if (code == Postfix.OTHER) { // convert constant to Real object with "CONSTANT" RealType stack.push(new Real(CONSTANT, d.doubleValue())); } else { // constant is a function counter stack.push(new Real(d.doubleValue())); } } } } // return the final answer Thing answer = popStack(stack); if (!stack.empty()) { evalError("Syntax error in formula (leftover objects on stack)"); } // return answer in local form if (answer instanceof Data) { try { answer = ((Data) answer).local(); // remove "constant" tag if final answer is a constant if (answer instanceof Real && ((Real) answer).getType() == CONSTANT) { answer = new Real(((Real) answer).getValue()); } } catch (VisADException exc) { evalError("The answer could not be converted to local data"); answer = null; } catch (RemoteException exc) { evalError("The answer could not be converted to local data (remote)"); answer = null; } } return answer; } }