//
// FormulaUtil.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.*;
import java.rmi.RemoteException;
import java.util.*;
import visad.*;
/** Contains a variety of useful methods related to the
visad.formula package.<P> */
public class FormulaUtil {
/** create a FormulaManager object with many commonly desired features */
public static FormulaManager createStandardManager() {
String[] binOps = {".", "^", "*", "/", "%", "+", "-"};
int[] binPrec = {200, 400, 600, 600, 600, 800, 800};
String[] binMethods = {
"visad.formula.FormulaUtil.dot(visad.TupleIface, visad.Real)",
"visad.Data.pow(visad.Data)",
"visad.Data.multiply(visad.Data)",
"visad.Data.divide(visad.Data)",
"visad.Data.remainder(visad.Data)",
"visad.Data.add(visad.Data)",
"visad.Data.subtract(visad.Data)"
};
String[] unaryOps = {"-"};
int[] unaryPrec = {500};
String[] unaryMethods = {"visad.Data.negate()"};
String[] functions = {
"abs", "acos", "acosDegrees", "asin", "asinDegrees", "atan", "atan2",
"atanDegrees", "atan2Degrees", "ceil", "combine", "cos", "cosDegrees",
"derive", "domainMultiply", "domainFactor", "exp", "extract", "floor",
"getSample", "linkx", "log", "max", "min", "negate", "rint", "round",
"sin", "sinDegrees", "sqrt", "tan", "tanDegrees"
};
String[] funcMethods = {
"visad.Data.abs()",
"visad.Data.acos()",
"visad.Data.acosDegrees()",
"visad.Data.asin()",
"visad.Data.asinDegrees()",
"visad.Data.atan()",
"visad.Data.atan2(visad.Data)",
"visad.Data.atanDegrees()",
"visad.Data.atan2Degrees(visad.Data)",
"visad.Data.ceil()",
"visad.FieldImpl.combine(visad.Field[])",
"visad.Data.cos()",
"visad.Data.cosDegrees()",
"visad.formula.FormulaUtil.derive(visad.Function, " +
"visad.formula.VRealType)",
"visad.FieldImpl.domainMultiply()",
"visad.formula.FormulaUtil.factor(" +
"visad.FieldImpl, visad.formula.VRealType)",
"visad.Data.exp()",
"visad.formula.FormulaUtil.extract(visad.Field, visad.Real)",
"visad.Data.floor()",
"visad.formula.FormulaUtil.brackets(visad.Field, visad.Real)",
"visad.formula.FormulaUtil.link(visad.formula.VMethod, " +
"java.lang.Object[])",
"visad.Data.log()",
"visad.Data.max(visad.Data)",
"visad.Data.min(visad.Data)",
"visad.Data.negate()",
"visad.Data.rint()",
"visad.Data.round()",
"visad.Data.sin()",
"visad.Data.sinDegrees()",
"visad.Data.sqrt()",
"visad.Data.tan()",
"visad.Data.tanDegrees()"
};
int implicitPrec = 200;
String[] implicitMethods = {
"visad.formula.FormulaUtil.implicit(visad.Function, visad.Real)",
"visad.Function.evaluate(visad.RealTuple)"
};
String preParseMethod = "visad.formula.FormulaUtil.preParse(" +
"java.lang.String, visad.formula.FormulaManager)";
FormulaManager f;
try {
f = new FormulaManager(binOps, binPrec, binMethods,
unaryOps, unaryPrec, unaryMethods, functions, funcMethods,
implicitPrec, implicitMethods, preParseMethod);
}
catch (FormulaException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
return null;
}
return f;
}
/** evaluate the dot operator */
public static Data dot(TupleIface t, Real r) {
Data d = null;
try {
d = t.getComponent((int) r.getValue());
}
catch (VisADException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
}
catch (RemoteException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
}
return d;
}
/** evaluate the derive function */
public static Data derive(Function f, VRealType rt) {
Data val = null;
try {
val = f.derivative(rt.getRealType(), Data.NO_ERRORS);
}
catch (VisADException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
}
catch (RemoteException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
}
return val;
}
/** evaluate the domainFactor function */
public static visad.Field factor(FieldImpl f, VRealType rt) {
visad.Field val = null;
try {
val = f.domainFactor(rt.getRealType());
}
catch (VisADException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
}
catch (RemoteException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
}
return val;
}
/** evaluate the extract function */
public static Data extract(visad.Field f, Real r) {
Data d = null;
try {
d = f.extract((int) r.getValue());
}
catch (VisADException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
}
catch (RemoteException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
}
return d;
}
/** evaluate the link function */
public static Data link(VMethod m, Object[] o) throws VisADException {
Data ans = null;
if (o != null) {
for (int i=0; i<o.length; i++) {
// convert VRealTypes to RealTypes
if (o[i] instanceof VRealType) {
o[i] = ((VRealType) o[i]).getRealType();
}
}
}
try {
ans = (Data) FormulaUtil.invokeMethod(m.getMethod(), o);
}
catch (ClassCastException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
throw new VisADException("Link error: invalid linked method");
}
catch (IllegalAccessException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
throw new VisADException("Link error: cannot access linked method");
}
catch (IllegalArgumentException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
throw new VisADException("Link error: bad method argument");
}
catch (InvocationTargetException exc) {
if (FormulaVar.DEBUG) exc.getTargetException().printStackTrace();
throw new VisADException("Link error: linked method threw an exception");
}
if (ans == null) {
throw new VisADException("Link error: linked method returned null data");
}
return ans;
}
/** evaluate implicit function syntax; e.g., A1(5) or A1(A2) */
public static Data implicit(Function f, Real r) {
Data value = null;
try {
value = f.evaluate(r);
}
catch (VisADException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
}
catch (RemoteException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
}
return value;
}
/** evaluate the bracket function; e.g., A1[5] or A1[A2] */
public static Data brackets(visad.Field f, Real r) {
Data value = null;
try {
RealType rt = (RealType) r.getType();
value = f.getSample((int) r.getValue());
}
catch (VisADException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
}
catch (RemoteException exc) {
if (FormulaVar.DEBUG) exc.printStackTrace();
}
return value;
}
/** number of link variables that have been created */
private static int linkNum = 0;
/** do some pre-computation parsing to a formula */
public static String preParse(String f, FormulaManager fm) {
// remove spaces
StringTokenizer t = new StringTokenizer(f, " ", false);
String s = "";
while (t.hasMoreTokens()) s = s + t.nextToken();
if (s.equals("")) return s;
// multi-pass pre-parse sequence
String os;
do {
os = s;
s = preParseOnce(os, fm);
}
while (!s.equals(os));
return s;
}
/** used by preParse */
private static String preParseOnce(String s, FormulaManager fm) {
// convert to lower case
String l = s.toLowerCase();
// scan entire string
int len = l.length();
boolean letter = false;
String ns = "";
for (int i=0; i<len; i++) {
if (!letter && i < len - 1 && l.substring(i, i+2).equals("d(")) {
// convert d(x)/d(y) notation to standard derive(x, y) notation
i += 2;
int s1 = i;
for (int paren=1; paren>0; i++) {
// check for correct syntax
if (i >= len) return s;
char c = l.charAt(i);
if (c == '(') paren++;
if (c == ')') paren--;
}
int e1 = i-1;
// check for correct syntax
if (i > len - 3 || !l.substring(i, i+3).equals("/d(")) return s;
i += 3;
int s2 = i;
for (int paren=1; paren>0; i++) {
// check for correct syntax
if (i >= len) return s;
char c = l.charAt(i);
if (c == '(') paren++;
if (c == ')') paren--;
}
int e2 = i-1;
ns = ns + "derive(" + s.substring(s1, e1) +
"," + s.substring(s2, e2) + ")";
i--;
}
else if (!letter && i < len - 4 && l.substring(i, i+5).equals("link(")) {
// evaluate link(code) notation and replace with link variable
i += 5;
int s1 = i;
try {
while (l.charAt(i) != '(') i++;
}
catch (ArrayIndexOutOfBoundsException exc) {
// incorrect syntax
return s;
}
i++;
int e1 = i-1;
int s2 = i;
for (int paren=2; paren>1; i++) {
// check for correct syntax
if (i >= len) return s;
char c = l.charAt(i);
if (c == '(') paren++;
if (c == ')') paren--;
}
int e2 = i-1;
// check for correct syntax
if (i >= len || l.charAt(i) != ')') return s;
String prestr = s.substring(s1, e1) + "(";
String str = prestr;
// parse method's arguments; determine if they are Data or RealType
String sub = s.substring(s2, e2);
StringTokenizer st = new StringTokenizer(sub, ",", false);
boolean first = true;
Vector v = new Vector();
while (st.hasMoreTokens()) {
String token = st.nextToken();
if (first) first = false;
else str = str + ",";
RealType rt = RealType.getRealTypeByName(token);
String sv = (rt == null ? "visad.Data" : "visad.RealType");
v.add(sv);
str = str + sv;
}
str = str + ")";
// obtain Method object
Method[] meths = FormulaUtil.stringsToMethods(new String[] {str});
if (meths[0] == null) {
// attempt to identify any matching methods by compressing
// some or all of the arguments into array form
int vlen = v.size();
Vector vstrs = new Vector();
for (int iv=0; iv<vlen; iv++) {
String si = (String) v.elementAt(iv);
int lv = iv;
String sl;
while (lv < vlen) {
sl = (String) v.elementAt(lv++);
if (!sl.equals(si)) {
break;
}
str = prestr;
first = true;
for (int j=0; j<vlen; j++) {
if (first) first = false;
else str = str + ",";
String sj = (String) v.elementAt(j);
str = str + sj;
if (iv == j) {
str = str + "[]";
j = lv - 1;
}
}
str = str + ")";
vstrs.add(str);
}
}
String[] strlist = new String[vstrs.size()];
vstrs.toArray(strlist);
meths = FormulaUtil.stringsToMethods(strlist);
int found = -1;
for (int j=0; j<meths.length && found < 0; j++) {
if (meths[j] != null) found = j;
}
if (found >= 0) meths[0] = meths[found];
else {
// could not find a matching method
return s;
}
}
// store method object in a link variable
String link = "link" + (++linkNum);
try {
fm.setThing(link, new VMethod(meths[0]));
}
// catch any errors setting the link variable
catch (FormulaException exc) {
return s;
}
catch (VisADException exc) {
return s;
}
catch (RemoteException exc) {
return s;
}
ns = ns + "linkx(" + link + "," + s.substring(s2, e2) + ")";
}
else if (!letter) {
int j = i;
char c = l.charAt(j++);
while (j < len && ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))) {
c = l.charAt(j++);
}
// check for end-of-string
if (j == len) return ns + s.substring(i, len);
if (c == '[') {
// convert x[y] notation to standard getSample(x, y) notation
int k = j;
for (int paren=1; paren>0; k++) {
// check for correct syntax
if (k >= len) return s;
c = l.charAt(k);
if (c == '[') paren++;
if (c == ']') paren--;
}
ns = ns + "getSample(" + s.substring(i, j-1) +
"," + s.substring(j, k-1) + ")";
i = k-1;
}
else ns = ns + s.charAt(i);
}
else {
// append character to new string
ns = ns + s.charAt(i);
}
char c = (i < len) ? l.charAt(i) : '\0';
letter = (c >= 'a' && c <= 'z');
}
return ns;
}
// useful methods for advanced reflection:
/** convert an array of strings of the form
"package.Class.method(Class, Class, ...)"
to an array of Method objects */
public static Method[] stringsToMethods(String[] strings) {
int len = strings.length;
Method[] methods = new Method[len];
for (int j=0; j<len; j++) {
// remove spaces
StringTokenizer t = new StringTokenizer(strings[j], " ", false);
String s = "";
while (t.hasMoreTokens()) s = s + t.nextToken();
// separate into two strings
t = new StringTokenizer(s, "(", false);
String pre = t.nextToken();
String post = t.nextToken();
// separate first string into class and method strings
t = new StringTokenizer(pre, ".", false);
String c = t.nextToken();
int count = t.countTokens();
for (int i=0; i<count-1; i++) c = c + "." + t.nextToken();
String m = t.nextToken();
// get argument array of strings
t = new StringTokenizer(post, ",)", false);
count = t.countTokens();
String[] a;
if (count == 0) a = null;
else a = new String[count];
int x = 0;
while (t.hasMoreTokens()) a[x++] = t.nextToken();
// convert result to Method object
Class clas = null;
try {
clas = Class.forName(c);
}
catch (ClassNotFoundException exc) {
if (FormulaVar.DEBUG) {
System.out.println("ERROR: Class " + c + " does not exist!");
}
methods[j] = null;
continue;
}
Class[] param;
if (a == null) param = null;
else param = new Class[a.length];
for (int i=0; i<count; i++) {
// hack to convert array arguments to correct form
if (a[i].endsWith("[]")) {
a[i] = "[L" + a[i].substring(0, a[i].length()-2);
while (a[i].endsWith("[]")) {
a[i] = "[" + a[i].substring(0, a[i].length()-2);
}
a[i] = a[i] + ";";
}
try {
param[i] = Class.forName(a[i]);
}
catch (ClassNotFoundException exc) {
if (FormulaVar.DEBUG) {
System.out.println("ERROR: Class " + a[i] +
" (" + i + ") does not exist!");
}
methods[j] = null;
continue;
}
}
Method method = null;
try {
method = clas.getMethod(m, param);
}
catch (NoSuchMethodException exc) {
if (FormulaVar.DEBUG) {
System.out.println("ERROR: Method " + m + " does not exist!");
}
methods[j] = null;
continue;
}
methods[j] = method;
}
return methods;
}
/** attempt to invoke a Method with the given Object arguments, performing
static method auto-detection and automatic array compression */
public static Object invokeMethod(Method m, Object[] o)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
Object obj;
Object[] args;
Class[] c = m.getParameterTypes();
int num = (o == null) ? 0 : o.length;
int len = -1;
int a = -1;
if (c != null) {
len = c.length;
for (int i=0; i<len; i++) {
if (c[i].isArray()) a = i;
}
}
if (Modifier.isStatic(m.getModifiers())) {
// static method
obj = null;
if (num > 0) {
if (a < 0) {
args = new Object[num];
System.arraycopy(o, 0, args, 0, num);
}
else {
// compress some of the arguments into array form
args = new Object[len];
if (a > 0) System.arraycopy(o, 0, args, 0, a);
Object array = Array.newInstance(c[a].getComponentType(), num-len+1);
System.arraycopy(o, a, array, 0, num-len+1);
args[a] = array;
if (a < len-1) System.arraycopy(o, num-len+a+1, args, a+1, len-a-1);
}
}
else args = null;
}
else {
// object method
if (num > 0) obj = o[0];
else {
// invalid object method
return null;
}
if (num > 1) {
if (a < 0) {
args = new Object[num-1];
System.arraycopy(o, 1, args, 0, num-1);
}
else {
// compress some of the arguments into array form
args = new Object[len];
if (a > 0) System.arraycopy(o, 1, args, 0, a);
Object array = Array.newInstance(c[a].getComponentType(), num-len);
System.arraycopy(o, a+1, array, 0, num-len);
args[a+1] = array;
if (a < len-1) System.arraycopy(o, num-len+a+1, args, a+1, len-a-1);
}
}
else args = null;
}
return m.invoke(obj, args);
}
}