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)