You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gr...@apache.org on 2009/06/19 07:56:14 UTC

svn commit: r786382 - in /commons/proper/jexl/branches/2.0/src: java/org/apache/commons/jexl/ java/org/apache/commons/jexl/parser/ test/org/apache/commons/jexl/ test/org/apache/commons/jexl/util/introspection/

Author: grobmeier
Date: Fri Jun 19 05:56:13 2009
New Revision: 786382

URL: http://svn.apache.org/viewvc?rev=786382&view=rev
Log:
JEXL-15: Needs definable functions
Contributed by Henri Biestro

Modified:
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Debugger.java
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Interpreter.java
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlEngine.java
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/Parser.jjt
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/VisitorAdapter.java
    commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/Jexl.java
    commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/MethodTest.java
    commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/util/introspection/MethodKeyTest.java

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Debugger.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Debugger.java?rev=786382&r1=786381&r2=786382&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Debugger.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Debugger.java Fri Jun 19 05:56:13 2009
@@ -33,6 +33,7 @@
 import org.apache.commons.jexl.parser.ASTFalseNode;
 import org.apache.commons.jexl.parser.ASTFloatLiteral;
 import org.apache.commons.jexl.parser.ASTForeachStatement;
+import org.apache.commons.jexl.parser.ASTFunctionNode;
 import org.apache.commons.jexl.parser.ASTGENode;
 import org.apache.commons.jexl.parser.ASTGTNode;
 import org.apache.commons.jexl.parser.ASTIdentifier;
@@ -43,7 +44,7 @@
 import org.apache.commons.jexl.parser.ASTLTNode;
 import org.apache.commons.jexl.parser.ASTMapEntry;
 import org.apache.commons.jexl.parser.ASTMapLiteral;
-import org.apache.commons.jexl.parser.ASTMethod;
+import org.apache.commons.jexl.parser.ASTMethodNode;
 import org.apache.commons.jexl.parser.ASTModNode;
 import org.apache.commons.jexl.parser.ASTMulNode;
 import org.apache.commons.jexl.parser.ASTNENode;
@@ -361,7 +362,24 @@
     }
 
     /** {@inheritDoc} */
-    public Object visit(ASTMethod node, Object data) {
+    public Object visit(ASTFunctionNode node, Object data) {
+        int num = node.jjtGetNumChildren();
+        accept(node.jjtGetChild(0), data);
+        builder.append(":");
+        accept(node.jjtGetChild(1), data);
+        builder.append("(");
+        for (int i = 2; i < num; ++i) {
+            if (i > 2) {
+                builder.append(", ");
+            }
+            accept(node.jjtGetChild(i), data);
+        }
+        builder.append(")");
+        return data;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTMethodNode node, Object data) {
         int num = node.jjtGetNumChildren();
         accept(node.jjtGetChild(0), data);
         builder.append("(");

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=786382&r1=786381&r2=786382&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 Fri Jun 19 05:56:13 2009
@@ -41,6 +41,7 @@
 import org.apache.commons.jexl.parser.ASTExpression;
 import org.apache.commons.jexl.parser.ASTExpressionExpression;
 import org.apache.commons.jexl.parser.ASTFalseNode;
+import org.apache.commons.jexl.parser.ASTFunctionNode;
 import org.apache.commons.jexl.parser.ASTFloatLiteral;
 import org.apache.commons.jexl.parser.ASTForeachStatement;
 import org.apache.commons.jexl.parser.ASTGENode;
@@ -53,7 +54,7 @@
 import org.apache.commons.jexl.parser.ASTLTNode;
 import org.apache.commons.jexl.parser.ASTMapEntry;
 import org.apache.commons.jexl.parser.ASTMapLiteral;
-import org.apache.commons.jexl.parser.ASTMethod;
+import org.apache.commons.jexl.parser.ASTMethodNode;
 import org.apache.commons.jexl.parser.ASTModNode;
 import org.apache.commons.jexl.parser.ASTMulNode;
 import org.apache.commons.jexl.parser.ASTNENode;
@@ -91,6 +92,8 @@
     private final Uberspect uberspect;
     /** the arithmetic handler. */
     private final Arithmetic arithmetic;
+    /** The map of registered functions. */
+    private final Map<String,Object> functions;
     /** The context to store/retrieve variables. */
     private final JexlContext context;
     /** dummy velocity info. */
@@ -104,9 +107,10 @@
      * @param uber the helper to perform introspection,
      * @param arith the arithmetic handler
      */
-    public Interpreter(Uberspect uber, Arithmetic arith, JexlContext context) {
+    public Interpreter(Uberspect uber, Arithmetic arith, Map<String,Object> functions, JexlContext context) {
         this.uberspect = uber;
         this.arithmetic = arith;
+        this.functions = functions;
         this.context = context;
     }
 
@@ -608,9 +612,22 @@
     }
 
     /** {@inheritDoc} */
-    public Object visit(ASTMethod node, Object data) {
-        // objectNode 0 is the identifier (method name), the others are parameters.
+    public Object visit(ASTMethodNode node, Object data) {
         // the object to invoke the method on should be in the data argument
+        if (data == null) {
+            // if the first child of the (ASTReference) parent,
+            // it is considered as calling a 'top level' function
+            if (node.jjtGetParent().jjtGetChild(0) == node) {
+                data = functions.get(null);
+                if (data == null) {
+                    throw new JexlException(node, "no default function namespace");
+                }
+            }
+            else {
+                throw new JexlException(node, "attempting to call method on null");
+            }
+        }
+        // objectNode 0 is the identifier (method name), the others are parameters.
         String methodName = ((ASTIdentifier) node.jjtGetChild(0)).image;
 
         // get our params
@@ -654,6 +671,56 @@
     }
 
     /** {@inheritDoc} */
+    public Object visit(ASTFunctionNode node, Object data) {
+        // objectNode 0 is the prefix
+        String prefix = ((ASTIdentifier) node.jjtGetChild(0)).image;
+        Object functor = functions.get(prefix);
+        if (functor == null)
+            throw new JexlException(node, "no such function " + prefix);
+        // objectNode 1 is the identifier , the others are parameters.
+        String methodName = ((ASTIdentifier) node.jjtGetChild(1)).image;
+
+        // get our params
+        int paramCount = node.jjtGetNumChildren() - 2;
+        Object[] params = new Object[paramCount];
+        for (int i = 0; i < paramCount; i++) {
+            params[i] = node.jjtGetChild(i + 2).jjtAccept(this, null);
+        }
+
+        try {
+            VelMethod vm = getUberspect().getMethod(functor, 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] = arithmetic.narrow((Number) param);
+                    }
+                }
+                vm = getUberspect().getMethod(functor, methodName, params, DUMMY);
+                if (vm == null) {
+                    return null;
+                }
+            }
+
+            return vm.invoke(functor, params);
+        }
+        catch (InvocationTargetException e) {
+            Throwable t = e.getTargetException();
+            if (!(t instanceof Exception)) {
+                t = e;
+            }
+            throw new JexlException(node, "function invocation error", t);
+        }
+        catch (Exception e) {
+            throw new JexlException(node, "function error", e);
+        }
+    }
+
+    /** {@inheritDoc} */
     public Object visit(ASTModNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlEngine.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlEngine.java?rev=786382&r1=786381&r2=786382&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlEngine.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlEngine.java Fri Jun 19 05:56:13 2009
@@ -22,6 +22,9 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.StringReader;
+import java.util.Map;
+import java.util.Collections;
+import java.util.HashMap;
 import java.net.URL;
 import java.net.URLConnection;
 import org.apache.commons.logging.*;
@@ -66,6 +69,11 @@
      * return null
      */
     boolean silent = true;
+
+    /**
+     *  The map of 'prefix:function' to object implementing the function.
+     */
+    protected Map<String,Object> functions = Collections.EMPTY_MAP;
     /**
      * ExpressionFactory & ScriptFactory need a singleton and this is the package
      * instance fulfilling that pattern.
@@ -76,18 +84,21 @@
      * Creates a default engine
      */
     public JexlEngine() {
-        this(null, null, null);
+        this(null, null, null, null);
     }
 
     /**
      * Creates a JEXL engine using the provided {@link Uberspect}, (@link Arithmetic) and logger.
      * @param uberspect to allow different introspection behaviour
      * @param arithmetic to allow different arithmetic behaviour
+     * @param funcs an optional map of functions (@see setFunctions)
      * @param log the logger for various messages
      */
-    public JexlEngine(Uberspect uberspect, Arithmetic arithmetic, Log log) {
+    public JexlEngine(Uberspect uberspect, Arithmetic arithmetic, Map<String,Object> funcs, Log log) {
         this.uberspect = uberspect == null? Introspector.getUberspect() : uberspect;
         this.arithmetic = arithmetic == null? new JexlArithmetic() : arithmetic;
+        if (funcs != null)
+            this.functions = funcs;
         if (log == null)
             log = LogFactory.getLog(JexlEngine.class);
         if (log == null)
@@ -109,7 +120,46 @@
     public boolean isSilent() {
         return this.silent;
     }
+    
+    /**
+     * Sets the map of function namespaces.
+     * <p>
+     * It should be defined once not modified afterwards since it might be shared
+     * between multiple engines evaluating expressions concurrently.
+     * </p>
+     * <p>
+     * Each entry key is used as a prefix, each entry value used as a bean implementing
+     * methods; an expression like 'nsx:method(123)' will thus be solved by looking at
+     * a registered bean named 'nsx' that implements method 'method' in that map.
+     * If all methods are static, you may use the bean class instead of an instance as value.
+     * </p>
+     * <p>
+     * The key or prefix allows to retrieve the bean that plays the role of the namespace.
+     * If the prefix is null, the namespace is the top-level namespace allowing to define
+     * top-level user defined functions ( ie: myfunc(...) )
+     * </p>
+     * <p>
+     * Note that you can always use a variable implementing methods & use
+     * the 'var.func(...)' syntax if you need more dynamic constructs.
+     * </p>
+     * @param funcs the map of functions that should not mutate after the call; if null
+     * is passed, the empty collection is used.
+     */
+    public void setFunctions(Map<String, Object> funcs) {
+        functions = funcs != null? funcs : Collections.EMPTY_MAP;
+    }
 
+
+    /**
+     * Retrieves the map of function namespaces.
+     *
+     * @return the map passed in setFunctions or the empty map if the
+     * original was null.
+     */
+    public Map<String, Object> getFunctions() {
+        return functions;
+    }
+    
     /**
      * Creates an Expression from a String containing valid
      * JEXL syntax.  This method parses the expression which
@@ -232,7 +282,7 @@
      * Creates an interpreter
      */
     protected Interpreter createInterpreter(JexlContext context) {
-        return new Interpreter(uberspect, arithmetic, context);
+        return new Interpreter(uberspect, arithmetic, functions, context);
     }
     /**
      * Trims the expression and adds a semi-colon if missing.

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/Parser.jjt
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/Parser.jjt?rev=786382&r1=786381&r2=786382&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/Parser.jjt (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/Parser.jjt Fri Jun 19 05:56:13 2009
@@ -421,9 +421,14 @@
     Parameter() "=>" Parameter()
 }
 
-void Method() : {}
+void Method() #MethodNode: {}
 {
-   Identifier() "("[ Parameter() ( "," Parameter() )* ] ")"
+   Identifier() "("[ Parameter() ( "," Parameter() )* ] ")"  
+}
+
+void Function() #FunctionNode: {}
+{
+   Identifier() ":" Identifier() "("[ Parameter() ( "," Parameter() )* ] ")"
 }
 
 void ArrayAccess() : {}
@@ -431,6 +436,13 @@
     Identifier() ("[" ( LOOKAHEAD(3) Expression() | IntegerLiteral() | Reference() ) "]")+
 }
 
+void AnyMethod() #void : {}
+{
+    LOOKAHEAD("size") SizeMethod()
+  |
+    LOOKAHEAD(Identifier() "(") Method()
+}
+
 void SizeMethod() : {}
 {
     "size" "(" ")"
@@ -438,12 +450,16 @@
 
 void Reference() : {}
 {
-  (LOOKAHEAD(Identifier() "[" ( Expression() | IntegerLiteral() | Reference()) "]") ArrayAccess() | Identifier() | MapLiteral())
+  (LOOKAHEAD(Identifier() "[" ( Expression() | IntegerLiteral() | Reference()) "]") ArrayAccess() |
+   LOOKAHEAD(Identifier() ":" Identifier() "(") Function() |
+   LOOKAHEAD(Identifier() "(") Method() |
+   Identifier() |
+   MapLiteral())
   (LOOKAHEAD(2) "."
     (
       LOOKAHEAD(Identifier() "[" ( Expression() | IntegerLiteral() | Reference()) "]") ArrayAccess() |
-//      (LOOKAHEAD(3) Method() | Identifier() |  IntegerLiteral() )
-      (LOOKAHEAD(3) Method() | SizeMethod() | Identifier() |  IntegerLiteral() )
+//      (LOOKAHEAD(3) AnyMethod() | Identifier() |  IntegerLiteral() )
+      (LOOKAHEAD(3) AnyMethod() | Identifier() |  IntegerLiteral() )
 
     )
   )*

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/VisitorAdapter.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/VisitorAdapter.java?rev=786382&r1=786381&r2=786382&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/VisitorAdapter.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/VisitorAdapter.java Fri Jun 19 05:56:13 2009
@@ -188,7 +188,13 @@
     }
 
     /** {@inheritDoc} */
-    public Object visit(ASTMethod node, Object data) {
+    public Object visit(ASTMethodNode node, Object data) {
+        node.dump(" ");
+        return node.childrenAccept(this, data);
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTFunctionNode node, Object data) {
         node.dump(" ");
         return node.childrenAccept(this, data);
     }

Modified: commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/Jexl.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/Jexl.java?rev=786382&r1=786381&r2=786382&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/Jexl.java (original)
+++ commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/Jexl.java Fri Jun 19 05:56:13 2009
@@ -31,6 +31,15 @@
         JexlContext context = new JexlContext() {
             public Map getVars() { return System.getProperties(); }
             public void setVars(Map map) { }
+
+            public void setFunctions(Map<String, Object> prefixes) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            public Map<String, Object> getFunctions() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
         };
         try {
             for (int i = 0; i < args.length; i++) {

Modified: commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/MethodTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/MethodTest.java?rev=786382&r1=786381&r2=786382&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/MethodTest.java (original)
+++ commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/MethodTest.java Fri Jun 19 05:56:13 2009
@@ -37,6 +37,21 @@
         }
     }
 
+    public static class Functor {
+        public int ten() {
+            return 10;
+        }
+        public int plus10(int num) {
+            return num + 10;
+        }
+        public static int TWENTY() {
+            return 20;
+        }
+        public static int PLUS20(int num) {
+            return num + 20;
+        }
+    }
+
     public void setUp() {
         asserter = new Asserter();
     }
@@ -85,4 +100,63 @@
         asserter.assertExpression("Boolean.valueOf('true')", Boolean.TRUE);
     }
 
+   public void testTopLevelCall() throws Exception {
+        java.util.Map funcs = new java.util.HashMap();
+        funcs.put(null, new Functor());
+        JexlEngine JEXL = new JexlEngine();
+        JEXL.setFunctions(funcs);
+
+        Expression e = JEXL.createExpression("ten()");
+        JexlContext jc = JexlHelper.createContext();
+        Object o = e.evaluate(jc);
+        assertEquals("Result is not 10", new Integer(10), o);
+
+        e = JEXL.createExpression("plus10(10)");
+        jc = JexlHelper.createContext();
+        o = e.evaluate(jc);
+        assertEquals("Result is not 20", new Integer(20), o);
+
+        e = JEXL.createExpression("plus10(ten())");
+        jc = JexlHelper.createContext();
+        o = e.evaluate(jc);
+        assertEquals("Result is not 20", new Integer(20), o);
+    }
+
+    public void testNamespaceCall() throws Exception {
+        java.util.Map funcs = new java.util.HashMap();
+        funcs.put("func", new Functor());
+        funcs.put("FUNC", Functor.class);
+        JexlEngine JEXL = new JexlEngine();
+        JEXL.setFunctions(funcs);
+
+        Expression e = JEXL.createExpression("func:ten()");
+        JexlContext jc = JexlHelper.createContext();
+        Object o = e.evaluate(jc);
+        assertEquals("Result is not 10", new Integer(10), o);
+
+        e = JEXL.createExpression("func:plus10(10)");
+        jc = JexlHelper.createContext();
+        o = e.evaluate(jc);
+        assertEquals("Result is not 20", new Integer(20), o);
+
+        e = JEXL.createExpression("func:plus10(func:ten())");
+        jc = JexlHelper.createContext();
+        o = e.evaluate(jc);
+        assertEquals("Result is not 20", new Integer(20), o);
+
+        e = JEXL.createExpression("FUNC:PLUS20(10)");
+        jc = JexlHelper.createContext();
+        o = e.evaluate(jc);
+        assertEquals("Result is not 30", new Integer(30), o);
+
+        e = JEXL.createExpression("FUNC:PLUS20(FUNC:TWENTY())");
+        jc = JexlHelper.createContext();
+        o = e.evaluate(jc);
+        assertEquals("Result is not 40", new Integer(40), o);
+    }
+
+    public static void main(String[] args) throws Exception {
+        new MethodTest().testTopLevelCall();
+    }
+
 }
\ No newline at end of file

Modified: commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/util/introspection/MethodKeyTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/util/introspection/MethodKeyTest.java?rev=786382&r1=786381&r2=786382&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/util/introspection/MethodKeyTest.java (original)
+++ commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/util/introspection/MethodKeyTest.java Fri Jun 19 05:56:13 2009
@@ -162,7 +162,7 @@
         
     }
     
-    static final int LOOP = 300;
+    static final int LOOP = 3;//00;
     
     public void testPerfKey() throws Exception {
         for(int l = 0; l < LOOP; ++l)