You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by di...@apache.org on 2007/10/25 23:14:34 UTC
svn commit: r588363 -
/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Interpreter.java
Author: dion
Date: Thu Oct 25 14:14:34 2007
New Revision: 588363
URL: http://svn.apache.org/viewvc?rev=588363&view=rev
Log:
more implemented
Modified:
commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Interpreter.java
Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Interpreter.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Interpreter.java?rev=588363&r1=588362&r2=588363&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Interpreter.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Interpreter.java Thu Oct 25 14:14:34 2007
@@ -17,40 +17,63 @@
package org.apache.commons.jexl;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
import java.util.Stack;
+import org.apache.commons.jexl.parser.ASTAddNode;
+import org.apache.commons.jexl.parser.ASTArrayAccess;
+import org.apache.commons.jexl.parser.ASTAssignment;
import org.apache.commons.jexl.parser.ASTBitwiseAndNode;
import org.apache.commons.jexl.parser.ASTBitwiseComplNode;
import org.apache.commons.jexl.parser.ASTBitwiseOrNode;
import org.apache.commons.jexl.parser.ASTBitwiseXorNode;
+import org.apache.commons.jexl.parser.ASTBlock;
+import org.apache.commons.jexl.parser.ASTEQNode;
+import org.apache.commons.jexl.parser.ASTEmptyFunction;
import org.apache.commons.jexl.parser.ASTExpression;
import org.apache.commons.jexl.parser.ASTExpressionExpression;
+import org.apache.commons.jexl.parser.ASTForeachStatement;
import org.apache.commons.jexl.parser.ASTIdentifier;
+import org.apache.commons.jexl.parser.ASTIfStatement;
import org.apache.commons.jexl.parser.ASTIntegerLiteral;
+import org.apache.commons.jexl.parser.ASTMethod;
+import org.apache.commons.jexl.parser.ASTMulNode;
import org.apache.commons.jexl.parser.ASTNullLiteral;
import org.apache.commons.jexl.parser.ASTReference;
+import org.apache.commons.jexl.parser.ASTReferenceExpression;
+import org.apache.commons.jexl.parser.ASTSizeFunction;
+import org.apache.commons.jexl.parser.ASTStatementExpression;
+import org.apache.commons.jexl.parser.ASTStringLiteral;
+import org.apache.commons.jexl.parser.ASTTrueNode;
+import org.apache.commons.jexl.parser.Node;
import org.apache.commons.jexl.parser.SimpleNode;
import org.apache.commons.jexl.parser.VisitorAdapter;
import org.apache.commons.jexl.util.Coercion;
import org.apache.commons.jexl.util.Introspector;
+import org.apache.commons.jexl.util.introspection.Info;
import org.apache.commons.jexl.util.introspection.Uberspect;
+import org.apache.commons.jexl.util.introspection.VelMethod;
+import org.apache.commons.jexl.util.introspection.VelPropertyGet;
/**
* Starting point for an interpreter of JEXL syntax.
- * @author Dion Gillard
*/
class Interpreter extends VisitorAdapter {
/** The uberspect. */
private Uberspect uberspect;
-
/** The context to store/retrieve variables. */
private JexlContext context;
-
/** the stack that holds values during expressions. */
private Stack valueStack;
-
- //private Resolver resolver;
+ /** dummy velocity info. */
+ private static final Info DUMMY = new Info("", 1, 1);
/**
* Create the interpreter with the default settings.
@@ -89,10 +112,81 @@
return uberspect;
}
+ /**
+ * Sets the context that contain variables.
+ * @param aContext a {link JexlContext}
+ */
public void setContext(JexlContext aContext) {
context = aContext;
}
+ // up to here
+ /** {@inheritDoc} */
+ public Object visit(ASTAddNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+
+ // the spec says 'and'
+ if (left == null && right == null) {
+ return new Long(0);
+ }
+
+ if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
+
+ // in the event that either is null and not both, then just make the null a 0
+ try {
+ double l = Coercion.coercedouble(left);
+ double r = Coercion.coercedouble(right);
+ return new Double(l + r);
+ } catch (java.lang.NumberFormatException nfe) {
+ // Well, use strings!
+ return left.toString().concat(right.toString());
+ }
+ }
+
+ // TODO: support BigDecimal/BigInteger too
+
+ // attempt to use Longs
+ try {
+ long l = Coercion.coercelong(left);
+ long r = Coercion.coercelong(right);
+ return new Long(l + r);
+ } catch (java.lang.NumberFormatException nfe) {
+ // Well, use strings!
+ return left.toString().concat(right.toString());
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTArrayAccess node, Object data) {
+ node.dump("+");
+ return node.childrenAccept(this, data);
+ }
+
+
+ /** {@inheritDoc} */
+ public Object visit(ASTAssignment node, Object data) {
+ node.dump("+");
+ // child 0 should be the variable (reference) to assign to
+ Node left = node.jjtGetChild(0);
+ Object result = null;
+ if (left instanceof ASTReference) {
+ ASTReference reference = (ASTReference) left;
+ left = reference.jjtGetChild(0);
+ // TODO: this only works for a Reference that has a single identifier as it's child
+ if (left instanceof ASTIdentifier) {
+ String identifier = ((ASTIdentifier) left).image;
+ result = node.jjtGetChild(1).jjtAccept(this, data);
+ context.getVars().put(identifier, result);
+ }
+ } else {
+ throw new RuntimeException("Trying to assign to something other than a reference: " + left);
+ }
+
+ return result;
+ }
+
+ /** {@inheritDoc} */
public Object visit(ASTBitwiseAndNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
@@ -102,15 +196,17 @@
return new Long(l & r);
}
+ /** {@inheritDoc} */
public Object visit(ASTBitwiseComplNode node, Object data) {
- node.dump(" ");
+ node.dump("+");
Object left = node.jjtGetChild(0).jjtAccept(this, data);
long l = Coercion.coercelong(left);
return new Long(~l);
}
+ /** {@inheritDoc} */
public Object visit(ASTBitwiseOrNode node, Object data) {
- node.dump(" ");
+ node.dump("+");
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
long l = Coercion.coercelong(left);
@@ -118,8 +214,9 @@
return new Long(l | r);
}
+ /** {@inheritDoc} */
public Object visit(ASTBitwiseXorNode node, Object data) {
- node.dump(" ");
+ node.dump("+");
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
long l = Coercion.coercelong(left);
@@ -127,30 +224,417 @@
return new Long(l ^ r);
}
+ /** {@inheritDoc} */
+ public Object visit(ASTBlock node, Object data) {
+ node.dump("+");
+ int numChildren = node.jjtGetNumChildren();
+ Object result = null;
+ for (int i = 0; i < numChildren; i++) {
+ result = node.jjtGetChild(i).jjtAccept(this, data);
+ }
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTEmptyFunction node, Object data) {
+ node.dump("+");
+ Object o = node.jjtGetChild(0).jjtAccept(this, data);
+
+ if (o == null) {
+ return Boolean.TRUE;
+ }
+
+ if (o instanceof String && "".equals(o)) {
+ return Boolean.TRUE;
+ }
+
+ if (o.getClass().isArray() && ((Object[]) o).length == 0) {
+ return Boolean.TRUE;
+ }
+
+ if (o instanceof Collection && ((Collection) o).isEmpty()) {
+ return Boolean.TRUE;
+ }
+
+ // Map isn't a collection
+ if (o instanceof Map && ((Map) o).isEmpty()) {
+ return Boolean.TRUE;
+ }
+
+ return Boolean.FALSE;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTEQNode node, Object data) {
+ node.dump("+");
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+
+ if (left == null && right == null) {
+ /*
+ * if both are null L == R
+ */
+ return Boolean.TRUE;
+ } else if (left == null || right == null) {
+ /*
+ * we know both aren't null, therefore L != R
+ */
+ return Boolean.FALSE;
+ } else if (left.getClass().equals(right.getClass())) {
+ return left.equals(right) ? Boolean.TRUE : Boolean.FALSE;
+ } else if (left instanceof Float || left instanceof Double
+ || right instanceof Float || right instanceof Double) {
+ Double l = Coercion.coerceDouble(left);
+ Double r = Coercion.coerceDouble(right);
+
+ return l.equals(r) ? Boolean.TRUE : Boolean.FALSE;
+ } else if (left instanceof Number || right instanceof Number
+ || left instanceof Character || right instanceof Character) {
+ return Coercion.coerceLong(left).equals(Coercion.coerceLong(right)) ? Boolean.TRUE
+ : Boolean.FALSE;
+ } else if (left instanceof Boolean || right instanceof Boolean) {
+ return Coercion.coerceBoolean(left).equals(
+ Coercion.coerceBoolean(right)) ? Boolean.TRUE
+ : Boolean.FALSE;
+ } else if (left instanceof java.lang.String || right instanceof String) {
+ return left.toString().equals(right.toString()) ? Boolean.TRUE
+ : Boolean.FALSE;
+ }
+
+ return left.equals(right) ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ /** {@inheritDoc} */
public Object visit(ASTExpression node, Object data) {
return node.jjtGetChild(0).jjtAccept(this, data);
}
+ /** {@inheritDoc} */
public Object visit(ASTExpressionExpression node, Object data) {
return node.jjtGetChild(0).jjtAccept(this, data);
}
+ /** {@inheritDoc} */
+ public Object visit(ASTForeachStatement node, Object data) {
+ node.dump("+");
+
+ Object result = null;
+ /* first child is the loop variable */
+ ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
+ ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
+ /* second child is the variable to iterate */
+ Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
+ // make sure there is a value to iterate on and a statement to execute
+ if (iterableValue != null && node.jjtGetNumChildren() >= 3) {
+ /* third child is the statement to execute */
+ SimpleNode statement = (SimpleNode) node.jjtGetChild(2);
+ // get an iterator for the collection/array etc via the introspector.
+ Iterator itemsIterator = getUberspect().getIterator(
+ iterableValue, DUMMY);
+ while (itemsIterator.hasNext()) {
+ // set loopVariable to value of iterator
+ Object value = itemsIterator.next();
+ context.getVars().put(loopVariable.image, value);
+ // execute statement
+ result = statement.jjtAccept(this, data);
+ }
+ }
+ return result;
+ }
+
+ /** {@inheritDoc} */
public Object visit(ASTIdentifier node, Object data) {
- node.dump(" ");
- return context.getVars().get(node.image);
+ node.dump("+");
+ String name = node.image;
+ if (data == null) {
+ return context.getVars().get(name);
+ } else {
+ // bean.property, map.key etc
+
+ // TODO: implement map stuff here
+ if (data instanceof Map) {
+ return ((Map) data).get(name);
+ }
+ // look up bean property of data and return
+ VelPropertyGet vg = getUberspect().getPropertyGet(data, node.image, DUMMY);
+
+ if (vg != null) {
+ try {
+ return vg.invoke(data);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ return null;
+ }
+
+ }
}
+ /** {@inheritDoc} */
+ public Object visit(ASTIfStatement node, Object data) {
+ node.dump("+");
+ Object result = null;
+ /* first child is the expression */
+ Object expression = node.jjtGetChild(0).jjtAccept(this, data);
+ if (Coercion.coerceboolean(expression)) {
+ // first child is true statement
+ result = node.jjtGetChild(1).jjtAccept(this, data);
+ } else {
+ // if there is a false, execute it. false statement is the second child
+ if (node.jjtGetNumChildren() == 3) {
+ result = node.jjtGetChild(2).jjtAccept(this, data);
+ }
+ }
+
+ return result;
+ }
+
+ /** {@inheritDoc} */
public Object visit(ASTIntegerLiteral node, Object data) {
return Integer.valueOf(node.image);
}
- /** visit a 'null'. */
+ /** {@inheritDoc} */
+ public Object visit(ASTMethod node, Object data) {
+ node.dump("+");
+ // child 0 is the identifier (method name), the others are parameters.
+ // the object to invoke the method on should be in the data argument
+ String methodName = ((ASTIdentifier) node.jjtGetChild(0)).image;
+
+ int paramCount = node.jjtGetNumChildren() - 1;
+
+ // get our params
+ Object[] params = new Object[paramCount];
+
+ try {
+ for (int i = 0; i < paramCount; i++) {
+ params[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
+ }
+
+ VelMethod vm = getUberspect().getMethod(data, methodName, params, DUMMY);
+ // DG: If we can't find an exact match, narrow the parameters and try again!
+ if (vm == null) {
+
+ // replace all numbers with the smallest type that will fit
+ for (int i = 0; i < params.length; i++) {
+ Object param = params[i];
+ if (param instanceof Number) {
+ params[i] = narrow((Number) param);
+ }
+ }
+ vm = getUberspect().getMethod(data, methodName, params, DUMMY);
+ if (vm == null) {
+ return null;
+ }
+ }
+
+ return vm.invoke(data, params);
+ } catch (InvocationTargetException e) {
+ Throwable t = e.getTargetException();
+
+ if (t instanceof Exception) {
+ throw new RuntimeException(t);
+ }
+
+ throw new RuntimeException(e);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTMulNode node, Object data) {
+ node.dump("+");
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+
+ // the spec says 'and', I think 'or'
+ if (left == null && right == null) {
+ return new Byte((byte) 0);
+ }
+
+ // if anything is float, double or string with ( "." | "E" | "e") coerce
+ // all to doubles and do it
+ if (left instanceof Float
+ || left instanceof Double
+ || right instanceof Float
+ || right instanceof Double
+ || (left instanceof String
+ && (((String) left).indexOf(".") != -1
+ || ((String) left).indexOf("e") != -1
+ || ((String) left).indexOf("E") != -1))
+ || (right instanceof String
+ && (((String) right).indexOf(".") != -1
+ || ((String) right).indexOf("e") != -1
+ || ((String) right).indexOf("E") != -1))) {
+ Double l = Coercion.coerceDouble(left);
+ Double r = Coercion.coerceDouble(right);
+
+ return new Double(l.doubleValue() * r.doubleValue());
+ }
+
+ /*
+ * otherwise to longs with thee!
+ */
+
+ long l = Coercion.coercelong(left);
+ long r = Coercion.coercelong(right);
+
+ return new Long(l * r);
+ }
+
+
+ /** {@inheritDoc} */
public Object visit(ASTNullLiteral node, Object data) {
return null;
}
+ /** {@inheritDoc} */
public Object visit(ASTReference node, Object data) {
- node.dump(" ");
+ // could be array access, identifier or map literal
+ // followed by zero or more ("." and array access, method, size, identifier or integer literal)
+ node.dump("+");
+ int numChildren = node.jjtGetNumChildren();
+ Object baseValue = node.jjtGetChild(0).jjtAccept(this, data);
+ if (numChildren == 1) {
+ return baseValue;
+ }
+ // pass first piece of data in and loop through children
+ Object result = baseValue;
+ for (int i = 1; i < numChildren; i++) {
+ result = node.jjtGetChild(i).jjtAccept(this, result);
+ // if we get null back a result, stop evaluating the rest of the expr
+ if (result == null) {
+ break;
+ }
+ }
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTReferenceExpression node, Object data) {
+ node.dump("+");
return node.jjtGetChild(0).jjtAccept(this, data);
}
+
+ /** {@inheritDoc} */
+ public Object visit(ASTSizeFunction node, Object data) {
+ node.dump("+");
+ Object val = node.jjtGetChild(0).jjtAccept(this, data);
+
+ if (val == null) {
+ throw new IllegalArgumentException("size() : null arg");
+ }
+
+ return new Integer(sizeOf(val));
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTStatementExpression node, Object data) {
+ node.dump("+");
+ return node.jjtGetChild(0).jjtAccept(this, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTStringLiteral node, Object data) {
+ return node.image;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTTrueNode node, Object data) {
+ return Boolean.TRUE;
+ }
+
+// other stuff
+
+ /**
+ * Given a Number, return back the value using the smallest type the result
+ * will fit into. This works hand in hand with parameter 'widening' in java
+ * method calls, e.g. a call to substring(int,int) with an int and a long
+ * will fail, but a call to substring(int,int) with an int and a short will
+ * succeed.
+ *
+ * @param original the original number.
+ * @return a value of the smallest type the original number will fit into.
+ * @since 1.1
+ */
+ private Number narrow(Number original) {
+ if (original == null || original instanceof BigDecimal || original instanceof BigInteger) {
+ return original;
+ }
+ Number result = original;
+ if (original instanceof Double || original instanceof Float) {
+ double value = original.doubleValue();
+ if (value <= Float.MAX_VALUE && value >= Float.MIN_VALUE) {
+ result = new Float(result.floatValue());
+ }
+ // else it was already a double
+ } else {
+ long value = original.longValue();
+ if (value <= Byte.MAX_VALUE && value >= Byte.MIN_VALUE) {
+ // it will fit in a byte
+ result = new Byte((byte) value);
+ } else if (value <= Short.MAX_VALUE && value >= Short.MIN_VALUE) {
+ result = new Short((short) value);
+ } else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {
+ result = new Integer((int) value);
+ }
+ // else it was already a long
+ }
+ return result;
+ }
+
+ /**
+ * Calculate the <code>size</code> of various types: Collection, Array, Map, String,
+ * and anything that has a int size() method.
+ *
+ * @param val the object to get the size of.
+ * @return the size of val
+ */
+ private int sizeOf(Object val) {
+ if (val instanceof Collection) {
+ return ((Collection) val).size();
+ } else if (val.getClass().isArray()) {
+ return Array.getLength(val);
+ } else if (val instanceof Map) {
+ return ((Map) val).size();
+ } else if (val instanceof String) {
+ return ((String) val).length();
+ } else {
+ // check if there is a size method on the object that returns an
+ // integer
+ // and if so, just use it
+ Object[] params = new Object[0];
+ //Info velInfo = new Info("", 1, 1);
+ VelMethod vm = uberspect.getMethod(val, "size", params, DUMMY);
+ if (vm != null && vm.getReturnType() == Integer.TYPE) {
+ Integer result;
+ try {
+ result = (Integer) vm.invoke(val, params);
+ } catch (Exception e) {
+ throw new RuntimeException("size() : error executing", e);
+ }
+ return result.intValue();
+ }
+ throw new IllegalArgumentException("size() : unknown type : " + val.getClass());
+ }
+ }
+
+ /**
+ * Test if the passed value is a floating point number, i.e. a float, double or string with ( "." | "E" | "e").
+ * @param val the object to be tested
+ * @return true if it is, false otherwise.
+ */
+ private boolean isFloatingPointNumber(Object val) {
+ if (val instanceof Float || val instanceof Double) {
+ return true;
+ }
+ if (val instanceof String) {
+ String string = (String) val;
+ return string.indexOf(".") != -1 || string.indexOf("e") != -1 || string.indexOf("E") != -1;
+ }
+ return false;
+ }
+
}