Return-Path: Delivered-To: apmail-commons-commits-archive@minotaur.apache.org Received: (qmail 81615 invoked from network); 19 Jun 2009 05:56:29 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 19 Jun 2009 05:56:29 -0000 Received: (qmail 94014 invoked by uid 500); 19 Jun 2009 05:56:40 -0000 Delivered-To: apmail-commons-commits-archive@commons.apache.org Received: (qmail 93925 invoked by uid 500); 19 Jun 2009 05:56:40 -0000 Mailing-List: contact commits-help@commons.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@commons.apache.org Delivered-To: mailing list commits@commons.apache.org Received: (qmail 93916 invoked by uid 99); 19 Jun 2009 05:56:40 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 19 Jun 2009 05:56:40 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 19 Jun 2009 05:56:35 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 6A76423888C2; Fri, 19 Jun 2009 05:56:14 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: 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/ Date: Fri, 19 Jun 2009 05:56:14 -0000 To: commits@commons.apache.org From: grobmeier@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20090619055614.6A76423888C2@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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 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 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 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 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. + *

+ * It should be defined once not modified afterwards since it might be shared + * between multiple engines evaluating expressions concurrently. + *

+ *

+ * 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. + *

+ *

+ * 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(...) ) + *

+ *

+ * Note that you can always use a variable implementing methods & use + * the 'var.func(...)' syntax if you need more dynamic constructs. + *

+ * @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 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 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 prefixes) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public Map 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)